I have the following nHibernate query that works well for me to pull an entity and eager fetch its sub collections.
var contactInfo = session.QueryOver<PeopleInfo>()
.Fetch(x => x.Addresses).Eager
.Fetch(x => x.EmailAddresses).Eager
.Fetch(x => x.PhoneNumbers).Eager
.Where(x => x.Id == contactInfoId)
.SingleOrDefault();
Now, let's say in Addresses that there is a field called Active,
is there a way to just return all the active addresses in one call?
I can filter it out after the fact, I'm just wondering if there's
a way to do it via query over.
Thanks!
Yes, you can do this, but you need to include some joins in your QueryOver query.
Under the hood, .Fetch is generating a bunch of LEFT JOINs to bring back the full list of PeopleInfo without excluding people that don't have any of the associated collections that you're fetching eagerly.
You can override the way the join is performed by performing a left join yourself.
For example, if you want to get all PeopleInfo, and only addresses that are Active, you could do the following:
Address addressAlias;
var addressRestriction = Restrictions.Where(() => address.Active);
session.QueryOver<PeopleInfo>()
.Left.JoinQueryOver(
pi => pi.Addresses, () => addressAlias, addressRestriction)
.Fetch(x => x.Addresses).Eager
.Fetch(x => x.EmailAddresses).Eager
.Fetch(x => x.PhoneNumbers).Eager
.Where(x => x.Id == contactInfoId)
.SingleOrDefault();
Now, since you're JOINing on Address and selecting out the entire PeopleInfo entity, you don't actually need to use .Fetch to pull back all of the Address fields.
session.QueryOver<PeopleInfo>()
.Left.JoinQueryOver(
x => x.Addresses, () => addressAlias, addressRestriction)
.Fetch(x => x.EmailAddresses).Eager
.Fetch(x => x.PhoneNumbers).Eager
.Where(x => x.Id == contactInfoId)
.SingleOrDefault();
The Address columns are included in the SELECT clause because you've joined on Address and are selecting out the entire PeopleInfo class.
These both generate the same SQL, but you should verify in a profiler. It should look something like this:
SELECT this_.* -- All PeopleInfo columns
addressali1_.*, -- All Address columns
emailaddre4_.* -- All email address columns
FROM PeopleInfo this_
LEFT OUTER JOIN address addressali1_
ON this_.id = addressali1_.personid
AND ( addressali1_.active = 1)
LEFT OUTER JOIN emailaddress emailaddre4_
ON this_.id = emailaddre4_.personid
WHERE this_.id = <your id>
Related
I have two lists. I am trying to write a LINQ query.
My lists: Products, Dealers.
var productList=_context.ProductList
.Where(x=> x.Date=>new DateTime(2021,02,01) ).ToList();
I want to add the T-SQL constraint (below) to my LINQ, I tried with foreach but I can not handle it.
DealerCode Not IN(Select DealerCode from Dealers D With (nolock) Where D.ProductId=P.ProductId)
I don't want to do like !x.DealerCode.Contains(.....), because my lists are so big and I will use this constraint in 8 different queries. I am searching How can I do that in a more efficient way.
Edit after comments:
T-SQL Query
Select * From ProductList P
Where Date='2021-02-01'
AND DealerCode Not IN(Select DealerCode from Dealers D With (nolock) Where D.ProductId=P.ProductId)
I am trying to write the query in LINQ
you can use this query
var query = _context.ProductList
.Where(a => a.Date != (_context.ProductList
.Join(_context.Dealers,
right => right.ProductId,
left => left.ProductId,
(right, left) => new
{
right = right,
left = left
})
.Where(x => x.left.Date == a.Date)
.Select(y => y.left.Date)).FirstOrDefault());
Add the appropriate Navigation Properties to your models. Products should have a navigation property called "Dealers" and Dealers should have a navigation property called "Products". Once that is complete, your query becomes:
var query = _context.ProductList
.Where(p => p.Date = new DateTime(2021,02,01))
.Where(p => !p.Dealers.Any());
Assuming of course you are looking for products that have no dealers for it yet. Or something similar. Hard to tell without the complete models for ProductList, Dealers, etc.
I know that in Linq I have to do the OrderBy after doing a Select - Distinct, but I'm trying to order by an Included entity property that get lost after the Select.
For example:
var accounts = _context.AccountUser
.Include(o => o.Account)
.Where(o => o.UserId == userId || o.Account.OwnerId == userId)
.OrderByDescending(o => o.LastAccessed)
.Select(o => o.Account)
.Distinct();
As I'm doing the Where by an or of two different parameters, there is a good chance to obtain duplicated results. That's why I'm using the Distinct.
The problem here is that after I do the Select, I don't have the LastAccessed property anymore because it doesn't belong to the selected entity.
I thing the structure of the AccountUser and Account can be inferred from the query itself.
If you have the bi-directional navigation properties set up:
var accountsQuery = _context.AccountUser
.Where(o => o.UserId == userId || o.Account.OwnerId == userId)
.Select(o => o.Account)
.Distinct()
.OrderByDescending(a => a.AccountUser.LastAccessed);
When Selecting the Account you do not need .Include() Keep in mind that any related entities that you access off the Account will be lazy-loaded. I recommend using a .Select() to extract either a flattened view model or a view model hierarchy so that the SQL loads all needed fields rather than either eager-loading everything or tripping lazy-load calls.
Since LINQ doesn't implement DistinctBy and LINQ to SQL doesn't implement Distinct that takes an IEqualityComparer, you must substiture GroupBy+Select instead:
var accounts = _context.AccountUser
.Include(o => o.Account)
.Where(o => o.UserId == userId || o.Account.OwnerId == userId)
.GroupBy(o => o.Account).Select(og => og.First())
.OrderByDescending(o => o.LastAccessed)
.Select(o => o.Account);
My model has a navigation property and this navigation property has another sub navigation property. I need to use a where clause on sub navigation property to filter results.
I'm trying to use linq query but unable to get the results
_context.Set<Job>().Include(x=>x.Premises).ThenInclude(y=>y.Station.Where(s=>s.)
The following sql join gives me desired results
select *
from [dbo].[JOB] J inner join
[dbo].[PREMISES] P on J.PremisesId = P.Id inner join
[dbo].[STATION] S on P.StationCode=S.StationCode
where S.StationCode = '001'
Any ideas?
Notice these similar LINQ statements:
var jobs = db.Jobs
.Include(j => j.Premises)
.Include(j => j.Premises.Select(p => p.Stations))
.ToList();
var stations = db.Stations
.Include(s => s.Premise)
.Include(s => s.Premise.Job)
.ToList();
While your return type is different, you are essentially holding the same data in memory. I could use the second to get all jobs too:
var jobs_from_stations = stations.Select(s => s.Premise.Job).Distinct();
Both jobs_from_stations and jobs will contain the exact same data.
There is a difference in filtering though.
If you were to add a Where() clause in this query, it would work differently.
The first query would filter in scope of the Job entity, whereas the second would filter in scope of the Station entity.
Since you are currently trying to filter based on a station property, that suggests using the second query:
var stations = db.Stations
.Include(s => s.Premise)
.Include(s => s.Premise.Job)
.Where(s => s.StationCode == "001")
.ToList();
If you want the return type to be a list of jobs:
var jobs = db.Stations
.Include(s => s.Premise)
.Include(s => s.Premise.Job)
.Where(s => s.StationCode == "001")
.Select(s => s.Premise.Job)
.Distinct()
.ToList();
Note that it would still be possible to use the first query instead, but it becomes more verbose and unwieldy:
var jobs = db.Jobs
.Include(j => j.Premises)
.Include(j => j.Premises.Select(p => p.Stations))
.Where(j => j.Premises.Any(p => p.Stations.Any(s => s.StationCode == "001")))
.ToList();
As a rule of thumb, I always try to start from the child and work my way up. As you see in the above example, it makes the filtering easier. But maybe you also noticed that it keeps the Include() statements simple too:
.Include(s => s.Premise)
.Include(s => s.Premise.Job)
instead of
.Include(j => j.Premises)
.Include(j => j.Premises.Select(p => p.Stations))
While these two examples are functionally equivalent, having to add a Select() for every level becomes very cumbersome if you want to include entities that are several relationships removed from where you started.
I have two models: Thing and ThingStatus. Thing has an Id and some other fields. ThingStatus is a model which stores Status enum corresponding to id of Thing. Now I want to fetch Things that have Status != Completed.
What I try to do now looks like this:
var unfinishedIds = session.QueryOver<ThingStatus>()
.Where(t => t.Status != StatusEnum.Completed)
.Select(t => t.Id)
.List<long>()
.ToArray();
var unfinishedThings = session.QueryOver<Thing>()
.WhereRestriction(t => t.Id)
.IsIn(unfinishedIds)
.List<Thing>();
As far as I understand, in such case unfinishedIds will be fetched from database and only after that used as a filter in unfinishedThings query. Is there any way to avoid that and have the query optimizer select the right way to do that? I've heard there are some futures available with nhibernate but I'm not sure how they'd help here.
You can use a subquery if you can't create a NHibernate relationship between the two entities. No relationship --> no JoinAlias (or JoinQueryOver) possible.
With a subquery:
var unfinishedIds = QueryOver.Of<ThingStatus>()
.Where(t => t.Status != StatusEnum.Completed)
.Select(t => t.Id);
var unfinishedThings = session.QueryOver<Thing>()
.WithSubquery.WhereProperty(t => t.Id).In(unfinishedIds)
.List<Thing>();
(note the use of QueryOver.Of<>)
The query is equivalent to writing:
SELECT * FROM Things WHERE Id IN (SELECT Id FROM ThingsStatuses WHERE Status <> 'Completed')
I have an object model where an Order contains many LineItems, and each LineItem has an associated Product. In the object model, these are one-way associations -- a LineItem does not know anything about its Order.
I want to query for orders that contain a line item with a product name matching a string, returning one row for each order (so that paging can be performed).
SELECT * FROM Orders
WHERE OrderID IN (
SELECT DISTINCT OrderID
FROM LineItems
INNER JOIN Products on LineItems.ProductID = Products.ProductID
WHERE Products.Name = 'foo'
)
Given that I have an ICriteria or an IQueryOver representing the subquery, how do I actually apply it to my root Order query?
var subquery = QueryOver.Of<LineItem>
.Where(l => l.Product.Name == "foo")
.TransformUsing(Transformers.DistinctRootEntity);
I've found plenty of examples that assume the root object in the query is on the "many" side of a one-to-many relationship, but I can't figure out how to add a restriction on something that the root object has many of.
I'd make it a bi-directional relationship between order and line item to allow efficient queries (reduce the number of joins required). But, if for some weird reason you can't, you'll need to start the sub-query from the Order...
LineItem lineItemAlias = null;
Product productAlias = null;
var subQuery = QueryOver.Of<Order>()
.JoinAlias(x => x.LineItems, () => lineItemAlias)
.JoinAlias(() => lineItemAlias.Product, () => productAlias)
.Where(() => productAlias.Name == "foo")
.Select(Projections.Group<Order>(x => x.Id));
var results = Session.QueryOver<Order>()
.WithSubquery.WhereProperty(x => x.Id).In(subQuery)
.List();
The direct translation of the SQL that you provided can be acheived using this
var subQuery =
QueryOver.Of<LineItem>(() => lineItem)
.JoinAlias(() => lineItem.Products, () => product)
.Where(() => product.Name == "foo")
.Select(Projections.Distinct(
Projections.Property(()=> lineItem.Order.Id)));;
var theQueryYouNeed =
QueryOver.Of<Orders>(() => order)
.WithSubquery.WherePropertyIn(() => order.Id).In(subQuery);
However if your LineItem entity does not have a Order Property then you cannot really use the subquery.
If you need to find
All Orders which have a LineItem where the Product Name is "foo" then
var theQueryYouNeed =
QueryOver.Of<Orders>(() => order)
.JoinAlias(() => order.LineItems, () => lineItem)
.JoinAlias(() => lineItem.Product, () => product)
.Where(() => product.Name == "foo")
.TransformUsing(new DistinctRootEntityResultTransformer())