one to many join - taking only the last one on the many part - entity-framework

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.

Related

Filtering on Many to Many relationship

I have 3 related entities with 1 to many relationships.
Account 1-* Collection 1-* items
Each of these has a many to many relationship with Users, (with join tables AccountManagers , CollectionManagers, ItemManagers)
I'm trying to get a list of Collections for a user if he's in any of the join tables.
I was thinking create 3 expressions and then merge the 3 results at the end and somehow remove duplicates.
It seemed like Lambda expressions are the way to go, but I'm still learning them.
I think the middle one is easier , like
db.Collections.where(C => C.CollectionManagers.UserID == CurrentUserID)
But how would you gather a list of collections from the users account manager and item manager entries?
Thanks in advance
Using LINQ, the Union operator will return only the unique Collection rows, so assembling each category and combining them should work.
For Items, I thought it would be most efficient to find all Items managed by the current user and then find all collections they belong to:
var iCollections = Items.Where(i => i.ItemManagers.Any(im => im.UserId == CurrentUserID)).SelectMany(i => Collections.Where(c => c.Items.Contains(i)));
It is also possible to do this the other way, e.g. find all Collections that contain an Item managed by the current user:
var iCollections = Collections.Where(c => c.Items.Any(i => i.ItemManagers.Any(im => im.UserId == CurrentUserID)));
For Collections, as you pointed out, you just need to find all collections where the current user manages the collection:
var cCollections = Collections.Where(c => c.CollectionManagers.Any(cm => cm.UserId == CurrentUserID));
For Accounts, we find all accounts managed by the current user and then all collections owned by the account:
var aCollections = Accounts.Where(a => a.AccountManagers.Any(am => am.UserId == CurrentUserID)).SelectMany(a => a.Collections);
Then you can Union the results together:
var CurrentUserCollections = iCollections.Union(cCollections).Union(aCollections);

Entity Framework: How do I invoke pairs of entities from a raw query

For instance, I have a query:
SELECT * FROM
persons
LEFT JOIN vehicles
ON persons.Id = vehicles.OwnerId
I would like execute this query on an EF data context and have array of pairs "person-vehicle". how do I do it?
Another example:
SELECT persons.*, COUNT(vehicles.*) as cnt FROM
persons
JOIN vehicles
ON persons.Id = vehicles.OwnerId
GROUP BY vehicles.Id
Here I want to have a dictionary of a person as a key and number of vehicles he owns as a value.
I know that these quesies are simple enough and it's better to avoid raw sql in these cases. But I want to know possibilities of raw query handling, because real life queries can be much more complex.
You probably want to do some reading ion LINQ to Entities. https://msdn.microsoft.com/en-us/library/vstudio/bb386964(v=vs.100).aspx
The first one is pretty basic:
var persons = context.Persons
.Include(p => p.Vehicles)
.ToList();
The second one is a little more advanced:
var persons = context.Persons
.Select(p => new { Person p, VehicleCount = p.Vehicles.Count() }
.ToList();
You could also do a group by which is described in the link.

Entity Framework Conditional Count of Navigation Property 2 levels down

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

Linq to Entities Distinct on Column without Anonymous Type

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

entity framework 4.0 multiple joins

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.