How join in linq to nhibernate - linq

I have a linq query in Nhibernate.
var q = SessionInstance.Query<Person>();
if (!String.IsNullOrEmpty(dto.FirstName))
q = q.Where(x => x.FirstName.Contains(dto.FirstName));
This query is for Search in persons list. I need to add a join between Person and Employee classes. for add a where condition on a property in Employee class.
For example it :
if (dto.Type == PersonEnumType.EmployeeType)
q = q.Where(employee => employee.Code.Contains(dto.Code));
How can I add something like it?
My sql query is similar this :
select * from Person_Table
left outer join Employee_Table on Person_Table.Id = Employee_Table.Person_id_fk
where Person_Table.FirstName like '%Phill%' and Employee_Table.Code like '332'

It's hard to tell how to do this without knowing your mappings, but it could be something like this:
q.Where(x => x.FirstName.Contains(dto.FirstName))
.Where(x => x.Employees.Any(emp => emp.Code.Contains(dto.Code)))
If there is a Person.Employees at all. But I must admit I don't know if Linq to NHibernate supports Any(). That probably depends on which vesion of L2N you use.
If this does not work you should try your luck with GroupJoin() (because of the outer join), but I'm even more insecure about solid L2N support for that one. As far as I can see it is in L2N since 3.0 beta, but whether it is reliable...?

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);

Linq to Entities Query explanation

Is there any way I can make this Linq to entities query in another way (better) and understand what I did?
First, can I have the string.jon() in the first part (select(p => new {...)?
Second, why do I need the first select to end with .ToList() for the string.join() to work?
The tables relation are as follow:
And here is the code:
Productos.Select(p => new {
Id = p.Id,
Code = p.CodigoProd,
Name = p.Nombre,
Cant = p.Inventario.Sum(i => i.Cantidad),
Pric = p.Inventario.OrderBy(i => i.Precio).Select (i => i.Precio).FirstOrDefault(),
cate = p.ProductosXCategoria.Select(pc => pc.CategoriasdeProducto.Nombre)
}).Where (p => p.Cant != null).ToList()
.Select (r => new {
r.Id, r.Code, r.Cant, r.Name, r.Pric, Categ = string.Join("-",r.cate)
})
the result is this (which is the result i expected to be):
IEnumerable<> (17 items)
**Id-- Code-- Cant-- Name-- Pric-- Categ**
1-- AXI-- 30-- Pepsi-- 10-- Granos
3-- ASI-- 38-- Carne blanca-- 12-- Granos-Limpieza
The query looks fine to me.
The reason you can't move the string.Join method to the first Select, is that LINQ-to-Entities ultimately has to be able to translate to SQL. string.Join has no direct translation to SQL, so it doesn't know how to translate your LINQ query to it. By calling ToList() first, you bring the results of the first Select into memory, where the subsequent Select works with Linq-to-Objects. Since Linq-to-Objects does not need to translate to SQL, it can operate directly on the results of the first query in memory.
Generally, you would want to put everything that would be better left to SQL before the ToList() call (such as filtering, sorting, averaging, grouping, etc.), and leave additional work that can't be translated to SQL (or isn't as efficient to do so) for after the results have been brought into local memory.

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.

complex orderby that links to another table

I have the following query to start with:
var query = from p in db.Products
from pc in p.NpProductCategories
where pc.CategoryId == categoryId
select p;
I'm applying some more filtering on it and in the end I want to sort the results:
if (orderBy == ProductSortingEnum.Name)
query = query.OrderBy(x => x.Name);
else
query = query.OrderBy(............);
My big problem (coming from not knowing linq too good) is the ELSE here. How can I sort results by a column that is not in the current result set? I would like to somehow link to another linq query in the orderby. The sorting I'm trying to achive is to link to NpProductVariants query using the ProductId to match between NpProductVariant and Products
and sort by the Price of the NpProductVariant
Assuming you have the relationship set up in the dbml...
For one to one (and many to one):
query = query.OrderBy(p => p.NpProductVariant.Price);
For one to many:
query = query.OrderBy(p => p.NpProductVariants.Select(v => v.Price).Max());
Also:
var query =
from p in db.Products
where p.NpProductCategories.Any(pc => pc.CategoryId == categoryId)
select p;
I think you can hook your Join to your query as long as it is returning the same thing. So maybe something like (I'm not 100 % sure since I haven't tried it):
query = from i1 in query
join i2 in query2 on i1.PropertyToJoin equals i2.PropertyToJoin
orderby i1.OrderProp1, i2.OrderProp2
select i1;
But I think it might be a good idea to check the generated sql so it is still effective.

Linq filter collection with EF

I'm trying to get Entity Framework to select an object and filter its collection at the same time. I have a JobSeries object which has a collection of jobs, what I need to do is select a jobseries by ID and filter all the jobs by SendDate but I can't believe how difficult this simple query is!
This is the basic query which works:
var q = from c in KnowledgeStoreEntities.JobSeries
.Include("Jobs.Company")
.Include("Jobs.Status")
.Include("Category")
.Include("Category1")
where c.Id == jobSeriesId
select c;
Any help would be appreciated, I've been trying to find something in google and what I want to do is here:http://blogs.msdn.com/bethmassi/archive/2009/07/16/filtering-entity-framework-collections-in-master-detail-forms.aspx
It's in VB.NET though and I couldn't convert it to C#.
EDIT: I've tried this now and it doesn't work!:
var q = from c in KnowledgeStoreEntities.JobSeries
.Include("Jobs")
.Include("Jobs.Company")
.Include("Jobs.Status")
.Include("Category")
.Include("Category1")
where (c.Id == jobSeriesId & c.Jobs.Any(J => J.ArtworkId == "13"))
select c;
Thanks
Dan
Include can introduce performance problems. Lazy loading is guaranteed to introduce performance problems. Projection is cheap and easy:
var q = from c in KnowledgeStoreEntities.JobSeries
where c.Id == jobSeriesId
select new
{
SeriesName = c.Name,
Jobs = from j in c.Jobs
where j.SendDate == sendDate
select new
{
Name = j.Name
}
CategoryName = c.Category.Name
};
Obviously, I'm guessing at the names. But note:
Filtering works.
SQL is much simpler.
No untyped strings anywhere.
You always get the data you need, without having to specify it in two places (Include and elsewhere).
No bandwith penalties for retrieving columns you don't need.
Free performance boost in EF 4.
The key is to think in LINQ, rather than in SQL or in materializing entire entities for no good reason as you would with older ORMs.
I've long given up on .Include() and implemented Lazy loading for Entity Framework

Resources