Using LINQ-to-objects to create a DataTable from a collection - vb.net

In my VB.NET code, I have something like this:
A collection of clsEmployees, which is made up of clsEmployee objects.
I would need a LINQ statement, such that it would return me a DataTable, made up of rows of fields of clsEmployee, which are supposedly firstname, lastname, employeeID, phone, city etc.
Also, the LINQ statement should return only those rows where phone is not null.

Something like
var aList = empList
.Where((e) => e.phone != null)
.Select((e) => new { firstname : e.firstname, lastname : e.lastname }); // etc
If you really need rows you can use new DataRow(...) in the select.

Related

Optimizing EF flattening

I have a similar case to the following:
Say there's a number of jobs to be done and for each job there's a history of workers where only one worker is active per job. There's three tables: the Job itself, a mapping table JobWorkers which holds the history of workers for a given job (including a datetime "To" which indicates whether still active (null) or when assignment was cancelled (end date)) and Workers which have a first and last name.
I'd like to query a list of all jobs and the first and last name of the currently assigned worker as flat model. This is the code I'm executing:
var jobExample = dbContext.Jobs.Select(j => new
{
j.JobId,
// ...some other columns from jobs table
j.JobWorker.FirstOrDefault(jw => jw.To == null).Worker.FirstName, // first name of currently assigned worker
j.JobWorker.FirstOrDefault(jw => jw.To == null).Worker.LastName // last name of currently assigned worker
}).First();
The following SQL query is generated:
SELECT TOP (1)
[Extent1].[JobId] AS [JobId],
[Extent3].[FirstName] AS [FirstName],
[Extent5].[LastName] AS [LastName]
FROM [tables].[Jobs] AS [Extent1]
OUTER APPLY (SELECT TOP (1)
[Extent2].[WorkerId] AS [WorkerId]
FROM [tables].[JobWorkers] AS [Extent2]
WHERE ([Extent1].[JobId] = [Extent2].[JobId]) AND ([Extent2].[To] IS NULL) ) AS [Limit1]
LEFT OUTER JOIN [tables].[Workers] AS [Extent3] ON [Limit1].[WorkerId] = [Extent3].[WorkerId]
OUTER APPLY (SELECT TOP (1)
[Extent4].[WorkerId] AS [WorkerId]
FROM [tables].[JobWorkers] AS [Extent4]
WHERE ([Extent1].[JobId] = [Extent4].[JobId]) AND ([Extent4].[To] IS NULL) ) AS [Limit2]
LEFT OUTER JOIN [tables].[Workers] AS [Extent5] ON [Limit2].[WorkerId] = [Extent5].[WorkerId]
As one can see there're two outer apply/left outer joins that are identical. I'd like to get rid of one of those to make the query more performant.
Note that the select statement is dynamically generated based on what information the user actually wants to query. But even if this didn't apply I'm not sure how to do this without having a hierarchic structure and then only afterwards flatten it in .NET
Thanks for your help and if I can improve this question in any way please comment.
You've probably seen that there are two types of LINQ methods: the ones that return IQueryable<...>, and the other ones.
Methods of the first group use deferred execution. This means, that the query is made, but not executed yet. Your database is not contacted.
Methods of the second group, like ToList(), FirstOrDefault(), Count(), Any(), will execute the query: they will contact the database, and fetch the data that is needed to calculate the result.
This is the reason, that you should try to postpone any method of the second group to as last as possible. If you do it earlier, and you do something LINQy after it, changes are that you fetch to much data, or, as in your case: that you do execute the same code twice.
The solution is: move your FirstOrDefault to a later moment.
var jobExample = dbContext.Jobs.Select(job => new
{
Id = job.JobId,
... // other job properties
ActiveWorker = job.JobWorkers
.Where(jobWorker => jobWorker.To == null)
.Select(worker => new
{
FirstName = worker.FirstName,
LastName = worker.LastName,
})
.FirstOrDefault(),
})
.FirstOrDefault();
The result is slightly different than yours:
Id = 10;
... // other Job properties
// the current active worker:
ActiveWorker =
{
FirstName = "John",
LastName = "Doe",
}
If you really want an object with Id / FirstName / LastName, add an extra Select before your final FirstOrDefault:
.Select(jobWithActiveWorker => new
{
Id = jobWithActiveWorker.Id,
... // other Job properties
// properties of the current active worker
FirstName = jobWithActiveWorker.FirstName,
LastName = jobWithActiveWorker.LastName,
})
.FirstOrDefault();
Personally I think that you should not mix Job properties with Worker properties, so I think the first solution: "Job with its currently active worker" is neater: the Job properties are separated from the Worker properties. You can see why that is important if you also wanted the Id of the active worker:
.Select(job => new
{
Id = job.JobId,
... // other job properties
ActiveWorker = job.JobWorkers
.Where(jobWorker => jobWorker.To == null)
.Select(jobworker => new
{
Id = jobworker.Id,
FirstName = jobworker.FirstName,
LastName = jobworker.LastName,
})
.FirstOrDefault(),
})
.FirstOrDefault();
Try rewriting your query like this:
var query =
from j in dbContext.Jobs
let ws = j.JobWorker
.Where(jw => jw.To == null)
.Select(jw => jw.Worker)
.Take(1)
from w in ws.DefaultIfEmpty()
select new
{
j.JobId,
// other properties
w.FirstName,
w.LastName,
};
The query processor probably could not have optimized any further to know it could use the subquery once.

Hibernate Search DSL and Lucene query on Multiple Fields

I'm not really sure how involved this might be, but could someone help me with below problem.
I'm trying to implement search functionality in my project based on employee firt and last name. I have used Spring Data REST and Hibernate Search for this purpose.
#Transactional
public search(String searchText) {
FullTextEntityManager fullTextEntityManager = org.hibernate.search.jpa.Search
.getFullTextEntityManager(entityManager);
QueryBuilder qb = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(Employee.class).get();
org.apache.lucene.search.Query luceneQuery = qb.keyword().wildcard()
.onFields("firstName", "middleName", "lastName").matching(searchText + "*").createQuery();
javax.persistence.Query jpaQuery = fullTextEntityManager.createFullTextQuery(luceneQuery, Employee.class);
List result = jpaQuery.getResultList();
List<EmployeeSearchDTO> listOfDTO = new ArrayList<>();
EmployeeSearchDTO employeeDTO;
Iterator<Employee> itr = result.iterator();
while (itr.hasNext()) {
Employee employee = itr.next();
employeeDTO = new EmployeeSearchDTO(employee);
listOfDTO.add(employeeDTO);
}
}
When I search "john doe" i expect the results should match the below two
FirstName : John LastName : Doe
FirstName : johnathan LastName : Doe
But that is not the case and I'm able to search only based on FirstName["john"] or LastName["doe"] but not with both.
How do I solve this, any pointers would be greatly appreciated. Thanksin advance.
You really want to create two queries, one against the first name and one against the last name and then combine them via the SHOULD operator. Something like
Query combinedQuery = querybuilder
.bool()
.should( firstNameQuery )
.should( lastNameQuery )
.createQuery();
This means you are looking for results where either of the queries match.

Add record in listGrid with variable

I am adding record in grid using startEditingNew method as below.
var COLUMN_NAME = {
name : "user_name",
lastname : "user_surname",
age : "user_age"
};
addDataToGrid : function (name, lastname, age){
MyGrid_Grid.startEditingNew({
COLUMN_NAME.name: name,
COLUMN_NAME.lastname: lastname,
COLUMN_NAME.age: age
});
}
But, my above function raise error and does not add record to grid.
If I use "user_name" string instead of "COLUMN_NAME.name" , It works fine.
How can I use variable as column name??
Thanks in advance
This is a javascript question, not a SmartClient one.
Anyway, if you need to use that approach, you may write something like:
addDataToGrid : function (name, lastname, age){
var record = {};
record[COLUMN_NAME.name] = name;
record[COLUMN_NAME.lastname] = lastname;
record[COLUMN_NAME.age] = age;
MyGrid_Grid.startEditingNew(record);
}
see also Rules for unquoted JavaScript Object Literal Keys?

Group By Sum Linq to SQL in C#

Really stuck with Linq to SQL grouping and summing, have searched everywhere but I don't understand enough to apply other solutions to my own.
I have a view in my database called view_ProjectTimeSummary, this has the following fields:
string_UserDescription
string_ProjectDescription
datetime_Date
double_Hours
I have a method which accepts a to and from date parameter and first creates this List<>:
List<view_UserTimeSummary> view_UserTimeSummaryToReturn =
(from linqtable_UserTimeSummaryView
in datacontext_UserTimeSummary.GetTable<view_UserTimeSummary>()
where linqtable_UserTimeSummaryView.datetime_Week <= datetime_To
&& linqtable_UserTimeSummaryView.datetime_Week >= datetime_From
select linqtable_UserTimeSummaryView).ToList<view_UserTimeSummary>();
Before returning the List (to be used as a datasource for a datagridview) I filter the string_UserDescription field using a parameter of the same name:
if (string_UserDescription != "")
{
view_UserTimeSummaryToReturn =
(from c in view_UserTimeSummaryToReturn
where c.string_UserDescription == string_UserDescription
select c).ToList<view_UserTimeSummary>();
}
return view_UserTimeSummaryToReturn;
How do I manipulate the resulting List<> to show the sum of the field double_Hours for that user and project between the to and from date parameters (and not separate entries for each date)?
e.g. a List<> with the following fields:
string_UserDescription
string_ProjectDescription
double_SumOfHoursBetweenToAndFromDate
Am I right that this would mean I would have to return a different type of List<> (since it has less fields than the view_UserTimeSummary)?
I have read that to get the sum it's something like 'group / by / into b' but don't understand how this syntax works from looking at other solutions... Can someone please help me?
Thanks
Steve
Start out by defining a class to hold the result:
public class GroupedRow
{
public string UserDescription {get;set;}
public string ProjectDescription {get;set;}
public double SumOfHoursBetweenToAndFromDate {get;set;}
}
Since you've already applied filtering, the only thing left to do is group.
List<GroupedRow> result =
(
from row in source
group row by new { row.UserDescription, row.ProjectDescription } into g
select new GroupedRow()
{
UserDescription = g.Key.UserDescription,
ProjectDescription = g.Key.ProjectDescription,
SumOfHoursBetweenToAndFromDate = g.Sum(x => x.Hours)
}
).ToList();
(or the other syntax)
List<GroupedRow> result = source
.GroupBy(row => new {row.UserDescription, row.ProjectDescription })
.Select(g => new GroupedRow()
{
UserDescription = g.Key.UserDescription,
ProjectDescription = g.Key.ProjectDescription,
SumOfHoursBetweenToAndFromDate = g.Sum(x => x.Hours)
})
.ToList();

Entity Framework and dynamic order by statements

I have been struggling to get this working. I wish to have an EF statement take in a column to order by. My original statement was this:
var Query = from P in DbContext.People
where P.BusinessUnits.Any(BU =>BU.BusinessUnitID == businessUnitId)
orderby P.LastName
select P;
And I changed this to the following:
var Query = from P in DbContext.People
where P.BusinessUnits.Any(BU =>BU.BusinessUnitID == businessUnitId)
orderby sortField
select P;
Where sortField is the column we wish to sort on, and is a string i.e. LastName. However, it does not appear to work, it does no sorting, and the outputted SQL string is completely wrong. Anyone got this working before?
you could try passing in an expression to your method with the following type:
Expression<Func<Person, object>> expr = p => p.LastName;
and then using linq extensions instead of linq expressions...
var Query =
DbContext.People
.Where(P => P.BusinessUnits.Any(BU =>BU.BusinessUnitID == businessUnitId))
.OrderBy(expr)
.ToList();
Your sort does not work because you are sorting on a string literal. It is not illegal, but it is not particularly useful either. You need to provide a sorting field through the API of IQueryable<T>, for example, like this:
var q = from P in DbContext.People
where P.BusinessUnits.Any(BU =>BU.BusinessUnitID == businessUnitId)
orderby P.LastName
select P;
if ("sortField".Equals("FirstName"))
q = q.OrderBy(p => p.FirstName);
else if ("sortField".Equals("LastName"))
q = q.OrderBy(p => p.LastName);
else if ("sortField".Equals("Dob"))
q = q.OrderBy(p => p.Dob);