This question already has answers here:
EF Core linq and conditional include and theninclude problem
(2 answers)
Closed 1 year ago.
I am creation a repo class to for my EF entities. I have a primary entity, lets call it Person. Person of course has collections Addresses, Cars, Pets, Loved Ones, and Secrets
When I use repo.GetPerson(id) I would like to include some sort of flag to indicate if the query should .Include() any of the collections.Something like [Flags] public enum AlsoInclude {Nothing=0, Addresses=1,Cars=2,Pets=4,LovedOnes=8,Secrets=16}; So GetPerson becomes Person GetPerson(Guid id, AlsoInclude alsoInclude)
Normally I would use people.Include(p=>p.Addresses).Include(p=>p.Cars)/*...*/;
I believe if I want to conditionally include the various collections, I would have to write 2^5 if tests for all the combinations.
Does something like IncludeIf(...) exists? Something where I could express
Person GetPerson(Guid id, AlsoInclude alsoInclude
=> people.IncludeIf(alsoInclude.HasFlag(AlsoInclude.Addresses),p=>p.Addresses)
.IncludeIf(alsoInclude.HasFlag(AlsoInclude.Cars),p=>p.Cars) /*...*/
.Single(p=>p.Key==id);
Thanks in Advance.
-Df5
You can very easily do optional includes by composing the query conditionally, somewhat like this:
Person GetPerson(Guid id, AlsoInclude alsoInclude)
{
var query = people;
if (alsoInclude.HasFlag(AlsoInclude.Addresses))
query = query.Include(p => p.Addresses);
if (alsoInclude.HasFlag(AlsoInclude.Cars))
query = query.Include(p => p.Cars)
/*Any other conditional clauses go here*/
return query.Single(p => p.Key == id);
}
You can then create an extension method that does this automatically to achieve exactly your theoretical IncludeIf operator.
Related
Just starting out with Entity Framework and am trying to work out how you would do something like this....
Say I have the following entities, Customers that have Orders that have OrderLineItems which are linked to Products. I would like to return the name of every customer with a count of the number of times they have ordered a particular product.
I have seen examples of using .Count() but these have always been for the first navigation property i.e. number of orders per customer.
Would appreciate some guidance here.
Something like this should work, where context is your DbContext instance.
It will return an IEnumerable<dynamic>, although obviously you could make a class to hold the results.
// The product to count
var productId = 12345;
context.Customers.Include("Orders.OrderLineItems.Products")
.Select(customer =>
new {
CustomerName = customer.Name,
ProductCount = customer.Orders
.SelectMany(o => o.OrderLineItems)
.SelectMany(i => i.Products.Where(p => p.Id = productId).Count()
});
The Include() extension method is useful, it will make sure that the resulting SQL query joins the relevant tables together - otherwise multiple queries would be executed for each customer (one to get orders, another for line items and a final one for products).
I am using Entity Framework 5.0 and I wish to return a list of objects, however, I want to perform a DISTINCT on one of the properties on each object within the list.
I know there are a few questions similar to mine already on Stackoverflow, however, I am still struggling with this one.
Currently my query looks like this
public IList<tblcours> GetAllCoursesByOrgID(int id)
{
return _UoW.tblcoursRepo.All.Where(c => c.tblCourseCategoryLinks.Any(cl => cl.tblUnitCategory.tblUnit.ParentID == id))
.OrderBy(c => c.CourseTitle)
.ToList();
}
However, I need to perform a DISTINCT on the property MainHeadingID to remove any objects already with the same ID, but still returning the entire object with all its properties.
Preferably, I would like to return IList, as you can see from my method, and not an Anonymous Type.
Any help with this is greatly appreciated.
Thanks.
Have you tried using GroupBy?
_UoW.tblcoursRepo.All.GroupBy(c => c.MainHeadingId)
.Select(g => g.FirstOrDefault())
This is my real world example.
I have 4 tables:
Person
Plan
Coverage
CoveredMembers
Each person can have many plans, each of those plans can have many coverages. Each of those coverages can have many CoveredMembers.
I need a query that will apply a filter on Plan.PlanType == 1 and CoveredMembers.TermDate == null.
This query should bring back any person who has a medical type plan that is not terminated.
This SQL statement would do just that:
SELECT Person.*, Plans.*, Coverages.*, CoveredMembers.*
FROM Person P
INNER JOIN Plan PL ON P.PersonID = PL.PersonID
INNER JOIN Coverage C on PL.PlanID = C.PlanID
INNER JOIN CoveredMember CM on C.CoverageID = CM.CoverageID
WHERE CM.TermDate = NULL AND PL.PlanType = 1
I have figured out how to do this using anonymous types, but I sometimes need to update the data and save back to the database - and anonymous types are read only.
I was given a solution that did work using JOIN but it only brought back the persons (albeit filtered the way I needed). I can then loop through each person:
foreach (var person in persons) {
foreach (var plan in person.Plans{
//do stuff
}
}
But wouldn't that make a db call for each iteration of the loop? I have 500 persons with 3 unterminated medical plans each, so it would call the db 1500 times?
This is why I want to bring the whole data tree from Persons to CoveredMembers back in one shot. Is this not possible?
I believe this is accomplished in two parts:
Your query to determine the people you wish to have returned based on your criteria as discussed in this question previously: Entity framework. Need help filtering results
Properly setting the navigation properties for entities you want brought together to be eagerly loaded: http://msdn.microsoft.com/en-us/data/jj574232.aspx
For example if your Person entity looks like:
public class Person {
public List<Plan> Plans {get; set;}
...
}
When returning data from the dbcontext you can also use explicit eager loading with the include option:
var people = context.People
.Include(p => p.Plans)
.ToList();
....
If these are nested - coverage is part of plan, etc (which it looks like, it goes something like):
var people = context.People
.Include(p => p.Plans.Select(pl=>pl.Coverage).Select(c=>c.CoveredMembers)))
.ToList();
....
I am making some assumptions about your data model here, and my code above probably needs a little tweaking.
EDIT:
I might need someone else to weigh in here, but I don't think you can add the where clause into an include like that (my example above leads you that way a bit by putting the include on the context object, instead return an IQueryable with your conditions set as solved in your first post (without a ToList() called on it) and then use the code you wrote above without the Where clauses:
From first post (you supplied different criteria in this one, but same concept)
var q = from q1 in dbContext.Parent
join q2 in dbContext.Children
on q1.key equals q2.fkey
join q3 in ........
where q4.col1 == 3000
select q1;
Then:
List<Person> people = q.Include(p => p.Plans
.Select(pl => pl.Coverages)
.Select(c => c.CoveredMembers).ToList();
Again, doing this without being able to troubleshoot - I am sure it would take me a few attempts to iron this one out too.
My model contains an Order (parent object) and Shipments (child object). The database table for these already have a surrogate key as an auto-increment primary key.
I have the business rule is that for each shipment in the order, we need to have an auto generated "counter" field -- e.g. Shipment 1, Shipment 2, Shipment 3, etc. Shipment model has properties: "ShipmentId", "OrderId", "ShipmentNumber". My attempted implemention is to have ShipmentNumber an int and in code(as opposed to database), query the Shipment collection and do max() + 1.
Here's a code snipet of what I'm doing.
Shipment newShipmentObj = // blah;
int? currentMaxId = myOrderObj.Shipments
.Select(x => (int?) x.ShipmentNumber)
.Max();
if (currentMaxId.HasValue)
newShipmentObj.ShipmentNumber = currentMaxId.Value + 1;
else
newShipmentObj.ShipmentNumber = 1; // 1st one
myOrderObj.Shipments.Add(newShipmentObj);
// etc.. rest of EF4 code
Is there a better way?
I don't really like this as I have the following problems because of potential transaction/concurrency issues.
My Order object also has a autoincrement "counter" -- e.g. Order 1, Order 2, Order 3, ... My Order model has properties: "OrderId", "CustomerId", "OrderNumber".
My design is that I have an OrderRepository but not a ShipmentRepository. The ShipmentRepository could query off the Order.Shipment collection... but with Orders, I have to query directly off the dbcontext, e.g.
int? currentMaxId = (_myDbContext)).Orders
.Where(x => x.CustomerId == 123456)
.Select(x => (int?)x.OrderNumber)
.Max();
However, the above part doesn't work well if I attempt to add multiple objects to the DbContext without committing/saving changes to the database. (i.e. the .Where() returns null... and only works if I use DbContext ".Local", which is not what I want.)
Help! Not sure what the best solution would be. Thanks!
you seem to already have shipmentid that is incremental. you can use it for you shipment number and maybe combined with current date as described here: How to implement gapless, user-friendly IDs in NHibernate? what you are trying to do with Max() is evil. Stay away from it as it can cause problems with getting the same shipment numbers for multiple shipments when the load is high
I'm quite a newbie in EF, so I'm sorry if my question has been answered before.. I just can't figure out the syntax..
I have two entities, Category & Product, where one category has many products.
I want to get all categories, with only their latest product (it has a date property named timestamp)
I have no idea how to do that. :-/
If possible I'd like to know the syntax of the two ways to write it, both the sql-like syntax, and the C# like syntax, e.g.:
ctx.Categories.Include("Products").ToList()
from c in ctx.Categories.Include("Products")
Thanks!
Here's the SQL-like way:
var categories =
from p in products
group p by p.Category into g
select new { Category = g.TheKey, LatestProduct = g.Max(p => p.TimeStamp) };
This is the Lambda-way (warning, untested):
var categories = products.GroupBy(p => p.Category)
.Select(g => new { Category = g.TheKey,
LatestProduct = g.Max(p => p.TimeStamp)});
A note on Categories.Include("Products"), you don't need this in your example. You use "Include" for eager-loading, so that for example if you had a list of Categories returned from EF, when you do Categories.Product you will get the associated product.
But all you require is a list of categories, and a single product for each one - which is already returned in the above LINQ query, so no need for Include.