LinQ to SQL and where is the choice for all database - linq

I have the source code like this:
var res = from s in Splitting
join c in Customer on s.CustomerId equals c.Id
where c.Id == customrId
&& c.CompanyId == companyId
select s;
When reviewing code, one member said that my code applies for only one SQL db, and advised me to use LinQ to Entity with Join so that it will work for all databases.
I don't understand, I think, even with other db, we will add it to Entity Framework. And the code below will work correct too, right?
Please advise.

There are two ways you can write your LINQ.
1.LINQ Query Expressions (query-syntax) (Which you have done)
var res = from s in Splitting
join c in Customer on s.CustomerId equals c.Id
where c.Id == customrId
&& c.CompanyId == companyId
select s;
2.Another is LINQ query extension methods (dot-syntax)
var res = Splitting.Join(Customer,
sp => sp.CustomerId,
cu => cu.Id,
(sp, cu) => new { sp, cu })
.Where(s => s.cu.Id == customrId && s.cu.CompanyId == companId)
.Select(s => s.sp);
For joins, I strongly prefer query-syntax.There are details that
query-syntax hides that can make it well worth embracing with the improvement to readability it brings.However query-syntax is somewhat
more limited than dot-syntax in other aspects.
dot-syntax is more concise but performing multiple table joins is a nightmare.The flip side is that there are a number of LINQ
operations that only exist within the dot-syntax: Single(),
First(), Count() etc.For these limitation of query-syntax you can use
dot-syntax.
N.B : At compile time, all are converted to Standard Query.

Related

Load only some elements of a nested collection efficiently with LINQ

I have the following LINQ query (using EF Core 6 and MS SQL Server):
var resultSet = dbContext.Systems
.Include(system => system.Project)
.Include(system => system.Template.Type)
.Select(system => new
{
System = system,
TemplateText = system.Template.TemplateTexts.FirstOrDefault(templateText => templateText.Language == locale.LanguageIdentifier),
TypeText = system.Template.Type.TypeTexts.FirstOrDefault(typeText => typeText.Language == locale.LanguageIdentifier)
})
.FirstOrDefault(x => x.System.Id == request.Id);
The requirement is to retrieve the system matching the requested ID and load its project, template and template's type info. The template has multiple TemplateTexts (one for each translated language) but I only want to load the one matching the requested locale, same deal with the TypeTexts elements of the template's type.
The LINQ query above does that in one query and it gets converted to the following SQL query (I edited the SELECT statements to use * instead of the long list of columns generated):
SELECT [t1].*, [t2].*, [t5].*
FROM (
SELECT TOP(1) [p].*, [t].*, [t0].*
FROM [ParkerSystems] AS [p]
LEFT JOIN [Templates] AS [t] ON [p].[TemplateId] = [t].[Id]
LEFT JOIN [Types] AS [t0] ON [t].[TypeId] = [t0].[Id]
LEFT JOIN [Projects] AS [p0] ON [p].[Project_ProjectId] = [p0].[ProjectId]
WHERE [p].[SystemId] = #__request_Id_1
) AS [t1]
LEFT JOIN (
SELECT [t3].*
FROM (
SELECT [t4].*, ROW_NUMBER() OVER(PARTITION BY [t4].[ReferenceId] ORDER BY [t4].[Id]) AS [row]
FROM [TemplateTexts] AS [t4]
WHERE [t4].[Language] = #__locale_LanguageIdentifier_0
) AS [t3]
WHERE [t3].[row] <= 1
) AS [t2] ON [t1].[Id] = [t2].[ReferenceId]
LEFT JOIN (
SELECT [t6].*
FROM (
SELECT [t7].*, ROW_NUMBER() OVER(PARTITION BY [t7].[ReferenceId] ORDER BY [t7].[Id]) AS [row]
FROM [TypeTexts] AS [t7]
WHERE [t7].[Language] = #__locale_LanguageIdentifier_0
) AS [t6]
WHERE [t6].[row] <= 1
) AS [t5] ON [t1].[Id0] = [t5].[ReferenceId]
which is not bad, it's not a super complicated query, but I feel like my requirement can be solved with a much simpler SQL query:
SELECT *
FROM [Systems] AS [p]
JOIN [Templates] AS [t] ON [p].[TemplateId] = [t].[Id]
JOIN [TemplateTexts] AS [tt] ON [p].[TemplateId] = [tt].[ReferenceId]
JOIN [Types] AS [ty] ON [t].[TypeId] = [ty].[Id]
JOIN [TemplateTexts] AS [tyt] ON [ty].[Id] = [tyt].[ReferenceId]
WHERE [p].[SystemId] = #systemId and tt.[Language] = 2 and tyt.[Language] = 2
My question is: is there a different/simpler LINQ expression (either in Method syntax or Query syntax) that produces the same result (get all info in one go) because ideally I'd like to not have to have an anonymous object where the filtered sub-collections are aggregated. For even more brownie points, it'd be great if the generated SQL would be simpler/closer to what I think would be a simple query.
Is there a different/simpler LINQ expression (...) that produces the same result
Yes (maybe) and no.
No, because you're querying dbContext.Systems, therefore EF will return all systems that match your filter, also when they don't have TemplateTexts etc. That's why it has to generate outer joins. EF is not aware of your apparent intention to skip systems without these nested data or of any guarantee that these systems don't occur in the database. (Which you seem to assume, seeing the second query).
That accounts for the left joins to subqueries.
These subqueries are generated because of FirstOrDefault. In SQL it always requires some sort of subquery to get "first" records of one-to-many relationships. This ROW_NUMBER() OVER construction is actually quite efficient. Your second query doesn't have any notion of "first" records. It'll probably return different data.
Yes (maybe) because you also Include data. I'm not sure why. Some people seem to think Include is necessary to make subsequent projections (.Select) work, but it isn't. If that's your reason to use Includes then you can remove them and thus remove the first couple of joins.
OTOH you also Include system.Project which is not in the projection, so you seem to have added the Includes deliberately. And in this case they have effect, because the entire entity system is in the projection, otherwise EF would ignore them.
If you need the Includes then again, EF has to generate outer joins for the reason mentioned above.
EF decides to handle the Includes and projections separately, while hand-crafted SQL, aided by prior knowledge of the data could do that more efficiently. There's no way to affect that behavior though.
This LINQ query is close to your SQL, but I'm afraid of correctness of the result:
var resultSet =
(from system in dbContext.Systems
from templateText in system.Template.TemplateTexts
where templateText.Language == locale.LanguageIdentifier
from typeText in system.Template.Type.TypeTexts
where typeText.Language == locale.LanguageIdentifier
select new
{
System = system,
TemplateText = templateText
TypeText = typeText
})
.FirstOrDefault(x => x.System.Id == request.Id);

What tools are there that help build expression trees for dynamic LINQ queries?

My project needs to let users build their own dynamic queries. From what I've read, Expression Trees are the way to go. However the syntax is rather complicated.
I envision having a GUI where users would be able to check tables, select columns, specify parameters,etc and then build a string such as:
var myQuery =
from P in context.Projects
join UA in context.UserAttributes on P.ProjectID equals UA.ProjectID
join UBA in context.UserBooleanAttributes on UA.UserAttributeID equals UBA.UserAttributeID
join U in context.Users on UBA.UserID equals U.UserID
where P.ProjectID == 1
where UBA.Value == true
where (UA.UserAttributeID == 1 || UA.UserAttributeID == 2)
select new { uba = U };
And store that in a queries table. To process the query, I was hoping there is some library out there that will magically do something like:
var result = magic(str);
foreach(var user in result)
Foo(user.Email);
In this example I know that all my queries would return Users, but for other queries I would probably have to use reflection or in another column specify the expected type in results.
I found one project called LinqTextQueryBuilder which looks interesting, but I wanted to see if there are other alternatives.

nHibernate 3 - Left Join re-Linq solution

I am trying to to run this Linq query below with nHibernate 3.
var items = from c in session.Query<tbla>()
join t in session.Query<tblb>() on c.Id equals t.SomeId into t1 // use left join on trades.
from t2 in t1.DefaultIfEmpty()
select new {item = c, desc = t2.Description};
This is the stock way to perform a left join in linq to my knowledge. However it's giving me an unsupported exception message. How can I achieve a basic left join without resorting back to HQL? This seems somewhat silly that an ORM as prevalent as nHibernate cannot support something as pedestrian as a left join.
[edit]
I've put the real answer to my own question below.
After further research on this; this is possible (although not obvious) to achieve in a strongly typed fashion using QueryOver. The trick is to use outer Query alias variables in conjunction with WithAlias, and TransformUsing. Here is an example that does left join with filtering and sorting.
// Query alias variables
entityTypeA anchorType = null;
entityTypeB joinedType = null;
var items = session.Query<entityTypeA>( ()=>anchorType )
.Left.JoinAlias(() => anchorType.FieldName, () => joinedType)
.WithSubquery.WhereProperty(e => e.FieldD).In(myFilterList)
// bind property mappings using WithAlias
.SelectList(list => list
.Select(e => e.FieldNameA).WithAlias( ()=> anchorType.FieldNameA )
.Select(e => e.FieldNameB).WithAlias( ()=> anchorType.FieldNameB )
)
.OrderBy(e => joinedType.FieldNameC).Desc
.TransformUsing(Transformers.AliasToBean<entityTypeA>()) // transform result to desired type.
.List<entityTypeA>();
It's not supported yet. HQL is your only choice at the moment.

Greater Than Condition in Linq Join

I had tried to join two table conditionally but it is giving me syntax error. I tried to find solution in the net but i cannot find how to do conditional join with condition. The only other alternative is to get the value first from one table and make a query again.
I just want to confirm if there is any other way to do conditional join with linq.
Here is my code, I am trying to find all position that is equal or lower than me. Basically I want to get my peers and subordinates.
from e in entity.M_Employee
join p in entity.M_Position on e.PostionId >= p.PositionId
select p;
You can't do that with a LINQ joins - LINQ only supports equijoins. However, you can do this:
var query = from e in entity.M_Employee
from p in entity.M_Position
where e.PostionId >= p.PositionId
select p;
Or a slightly alternative but equivalent approach:
var query = entity.M_Employee
.SelectMany(e => entity.M_Position
.Where(p => e.PostionId >= p.PositionId));
Following:
from e in entity.M_Employee
from p in entity.M_Position.Where(p => e.PostionId >= p.PositionId)
select p;
will produce exactly the same SQL you are after (INNER JOIN Position P ON E..PostionId >= P.PositionId).
var currentDetails = from c in customers
group c by new { c.Name, c.Authed } into g
where g.Key.Authed == "True"
select g.OrderByDescending(t => t.EffectiveDate).First();
var currentAndUnauthorised = (from c in customers
join cd in currentDetails
on c.Name equals cd.Name
where c.EffectiveDate >= cd.EffectiveDate
select c).OrderBy(o => o.CoverId).ThenBy(o => o.EffectiveDate);
If you have a table of historic detail changes including authorisation status and effective date. The first query finds each customers current details and the second query adds all subsequent unauthorised detail changes in the table.
Hope this is helpful as it took me some time and help to get too.

How to query (LINQ) multiple table association link?

I have tables association such as (CaseClient is a bridge table):
Cases has many CaseClients
Client has many CaseClients
ClientType has many CaseClient
The easiest way just use the view in database but I heard that with linq you can join this somehow? Or should I just created view in the database and linq query agains that view?
I am appreciated your comment
I think you want to use the Join method, from your bridging table and resolving each of your relationships. E.g.
// Where CaseId and TypeId are your members of CaseClient
var x = caseClients.Join( cases, cc => cc.CaseId, c => c.Id)
.Join( types, cc => cc.TypeId, t => t.Id)
.Select();
Above code untested (so far) and from memory. You may need to put a Select between the two joins.
Heres an adaptation of what I did for a very similar situation. Only the names have been changed to protect the innocent.
IEnumerable<Case> getCaseByClient(int client_id)
{
var ret = from c in Cases
join cc in CasesClients
on c.Id equals cc.ClientId
join cl in Clients
on cc.ClientId equals client_id
select c;
return ret;
}
of course this assumes your client_id field is an int, but thats easy enough to modify.

Resources