Entity Framework - Disctinct by property - c#

I have this code:
var models = await _assignment
.Include(a => a.Rule)
.Include(a => a.User)
.Select(x => new AssignmentResponse
{
Id = x.User.Id,
RuleId = x.RuleId ?? -1
})
.ApplyFilterAndPaginationAsync(_webQueryProcessor, request.Options, cancellationToken);
I am trying to get the first assignment for each user (there can be many ruleIds for one userId, but I only want the first).
My attempt (not working):
var models = await _assignment
.Include(a => a.Rule)
.Include(a => a.User)
.Distinct(a => a.User.Id)
.Select(x => new AssignmentResponse
{
Id = x.User.Id,
RuleId = x.RuleId ?? -1
})
.ApplyFilterAndPaginationAsync(_webQueryProcessor, request.Options, cancellationToken);
How can I achieve that without loading them all into memory?

So you have a table of Users and a table of Assignments, and there is a one-to-many relation between Users and Assignments: every User has zero or more Assignments, every Assignment belongs to exactly one User, namely the User that the foreign key refers to.
You also have a table of Rules. There is a relation between Assignments and Rules. I'm not sure about the kind of relation : does every Assignment have exactly one Rule? (one-to-many: Rules have zero or more Assignments, Assignment has foreign key to rule).
Or is there a many-to-many relation: every Assignment has zero or more Rules, and every Rule is a Rule in zero or more Assignments.
Because this is not part of your question, I won't go deep into this.
If you have followed the entity framework conventions, you will have classes like this:
public class User
{
public int Id {get; set;}
public string Name {get; set;}
... // other properties
// every User has zero or more Assignments (one-to-many)
public virtual ICollection<Assignment> Assignments {get; set;}
}
public class Assignment
{
public int Id {get; set;}
public string Name {get; set;}
public DateTime Date {get; set;}
... // other properties
// every Assignment is the assignment of exactly one user, using foreign key:
public int UserId {get; set;}
public virtual User User {get; set;}
// One-To-Many: Every assignment has one Rule using foreign key
public int RuleId {get; set;}
public virtual Rule Rule {get; set;}
// alternative: many-to-many: every assignment has zero or more Rules
public virtual ICollection<Rule> Rules {get; set;}
}
In Entity Framework, the non-virtual properties represent the columns of the tables; the virtual properties represent the relations between the tables (one-to-many, many-to-many).
The foreign key UserId is a real column in table Assignments, hence it is non-virtual. User is not a column in Assignment, but a relation with Assignment, hence it is declared virtual.
For completeness the DbContext:
public class WorkDbContext : DbContext
{
public DbSet<User> Users {get; set;}
public DbSet<AssignMent> Assignments {get; set;}
public DbSet<Rule> Rules {get; set;}
...
}
Because I followed the entity framework conventions, this is all that entity framework needs to detect the tables, the columns of the tables and the relations between the tables (one-to-many, many-to-many), including the foreign keys. Only if you want to deviate from the conventions, especially different identifiers, you need to use Attributes or fluent API.
I am trying to get the first assignment for each user
Problem: what is the first assignment?
Is that the assignment with the oldest Date?
is that the assignment with the lowest Id?
is that the assignment that comes first in the database? (fairly unpredictable)
Use virtual ICollections
When using entity framework to fetch data in a one-to-many relation, it is usually easier to use the virtual properties. Entity framework knows your relations, and creates the proper (Group-)Join for you.
using (var dbContext = new WorkDbContext())
{
var usersWithTheirFirstAssignment = dbContext.Users.Select(user => new
{
// Select only the User properties that you plan to use
Id = user.Id,
Name = user.Name,
...
// Get the "first" Assignment
FirstAssignment = ... // TODO: implement
})
.ToList();
}
To get the FirstAssignment, if that is the one with the oldest Date, OrderBy Date, before selecting the properties:
FirstAssignment = user.Assignments.OrderBy(assignment => assignment.Date)
.Select(assignment => new {...})
.FirstOrDefault();
Or if the first assignment is the one with the lowest Id:
FirstAssignment = user.Assignments.OrderBy(assignment => assignment.Id)
.Select(assignment => new {...})
.FirstOrDefault();
Or if the first assignment is just the one that is first in the database:
FirstAssignment = user.Assignments.Select(assignment => new {...})
.FirstOrDefault();
if you have a one-to-many relation, and you want for every "one" item its "many" subItem, start at the "one" in a Select and use the virtual ICollection to fetch the "many" subItems.
If, on the other hand, you want items, each item with its one and only parent that the foreign key refers to, start at the "many" side, and use the virtual property to select the "one" side properties.
Intermezzo: Why select only the properties that you plan to use?
Database management systems are extremely optimized in selecting data. One of the slower parts of the query is the transfer of the selected data from the DBMS to your local process. Hence it is wise to limit the amount of selected data as much as possible.
If you use Include to select related classes, the complete related subclass is selected, inclusive the foreign keys.
So if User [10] has 1000 Assignments, every Assignment will have a foreign key UserId with value 10. You will be transferring this same number 10 more than 1000 times. What a waste of processing power.
Another reason to use Select if you don't plan to change the fetched data, is that every DbContext has a ChangeTracker. If you fetch an table row without Select, the fetched object is stored in the ChangeTracker, together with a copy. You get a reference to the original. If you change values, the values of the original in the ChangeTracker are changed. When you use SaveChanges, all Originals in the ChangeTracker are compared by value with the copy, to see which items need to be updated.
So if you fetch 1000 users, your ChangeTracker will contain 1000 originals and 1000 copies. If you only change one before calling SaveChanges, the code must check every non-virtual property of each of these 1000 Users, while only one user will be changed. What a waste of processing power!
When querying data, always use Select and select only the properties that you actually plan to use. Only fetch complete classes or use Include if you plan to change the fetched data.
So in the Select above:
FirstAssignment = user.Assignments.OrderBy(...)
.Select(assignment => new
{
// Select only the Assignment properties that you plan to use
Id = assignment.Id,
Date = assignment.Date,
// don't Select the foreign key, you already know the value:
// UserId = assignment.UserId
Rule = new
{
// you know the drill by now: only the rule properties that you plan to use!
Id = assignment.Rule.Id,
Name = assignment.Rule.Name,
...
},
})
.FirstOrDefault(),
Alternative: use the foreign key in the Select
Although using the virtual ICollection seems to me the most intuitive solution, you could also use the foreign key to select the FirstAssignment:
var usersWithTheirFirstAssignment = dbContext.Users.Select(user => new
{
Id = user.Id,
Name = user.Name,
...
FirstAssignment = dbContext.Assignments
.Where(assignment => assignment.UserId == user.Id)
.Orderby(...)
.Select(...)
.FirstOrDefault(),
}
OrderBy and Select are the same as above. Entity Framework will create a GroupJoin similar to the one when using the virtual ICollection.
But I want to do the (Group-)Join myself!
Some people don't trust entity framework. They don't use the virtual ICollection, but prefer do the GroupJoin themselves. If you want to do that, you need to do something like:
// GroupJoin Users and Assignments:
var usersWithTheirFirstAssignment = dbContext.Users.GroupJoin(dbContext.Assignments,
user => user.Id, // from every user take the primary key
assignment => assignment.UserId, // from every assignment take the foreign key to the user
// parameter resultSelector: from every User and its zero or more assignments, make one new
(user, assignmentsOfThisUser) => new
{
Id = user.Id,
Name = user.Name,
...
// the first assignment: order and select like described above
FirstAssignment = assignmentsOfThisUser.OrderBy(...)
.Select(...)
.FirstOrDefault(),
}

Try to use group by
var models = await _assignment
.Include(a => a.Rule)
.Include(a => a.User)
.GroupBy(a => a.User.Id)
.Select(x => new AssignmentResponse
{
Id = x.Key,
RuleId = x.FirstOrDefault().RuleId ?? -1
})
.ApplyFilterAndPaginationAsync(_webQueryProcessor, request.Options, cancellationToken);
Moreover, I think that you don't need to include Rule? If you do not reference any field of it.

Related

Avoid unnecessary include statements in SQL database query for ASP.NET Core

There are some times where I have a user and would like to check whether they are in the organization that owns the item they are trying to access. Let's say the tables in my SQL server look like this:
Table: Organization
Table: User
Column: OrganizationID (foreign key to Organization table)
Table: Item
Column: OrganizationID (foreign key to Organization table)
Edit: however, my models do not have OrganizationID directly but rather the organization, as below:
public class Item{
public int ItemID { get; set; }
public string ItemSomeProperty { get; set; }
public virtual Organization Organization { get; set;}
}
And similar as above for User.
So anyways, in my Controller right now I am doing something to the effect of:
user = await _context.User.Include(c => c.Organization).Where(c => c.UserID == thisUserID).FirstOrDefaultAsync();
itemList = await _context.Items
.Include(c => c.Organization)
.Select(Some stuff)
.Where(c => c.Organization == user.Organization)
.ToListAsync();
Is there a way to avoid these unnecessary includes and just compare the actual IDs? I don't need any other info about the organization so I don't want an extra Include. I would also like to avoid doing extra migrations to add something like "public int OrganizationID" to each model; plus I'd need to awkwardly change the name of that so that it doesn't conflict with the existing FK in the table (which is also called OrganizationID).
In my mind, the ID is already present when I look at the tables with SQL Server Management Studio, since that's literally what the foreign key is - I just need to grab the value of the foreign key instead of having it assume I want the entire Organization object. But I am not a SQL expert.
Edited for clarity and a bit more detail on what the models look like.
In your model design, User and Item all have a property OrganizationID, So if you don't want to use include(xxx), You can use OrganizationID as a query condition.
//select User's fk
var userfk = _context.User
.Where(c => c.UserID == thisUserID)
.Select(c => c.OrganizationID)
.FirstOrDefaultAsync();
//if item and user has the same fk,so they are in the same Organization
List<string> itemList = _context.Items
.Where(c => c.OrganizationID == userfk)
.Select(c => c.ItemName)
.ToListAsync();

I'm pretty new to linq lambda. I have a MySQL query.which I need to convert to a lambda query

This is my SQL query:
SELECT TOP 1000
s.id, s.[index], s.staffini,
MIN(accessdate) AS [In],
MAX(accessdate) AS [Out],
sa.mode, sa.daytype
FROM
staffattendances AS sa
INNER JOIN
staffs AS s ON sa.staffid = s.id
GROUP BY
staffid, CAST(accessdate AS DATE), s.staffname,
sa.mode, sa.daytype, s.[index], s.staffini, s.id
How to convert this to a Linq lambda query?
var tmp = Context.staffAttendances
.Include(t => t.staff)
.GroupBy(s => new
{
s.StaffId, s.,
s.AccessDate,
s.staff.StaffName,
s.Mode, s.Daytype,
s.staff.index, s.staff.Id
})
.Select(x => new staffattdto
{
index = x.Key.index,
StaffIni = x.Key.StaffName,
In = x.Max(t => t.AccessDate),
Out = x.Max(t => t.AccessDate),
mode = x.Key.Mode,
daytype = x.Key.Daytype,
})
.OrderByDescending(t => t.In);
I personally prefer using linq query syntax for these kind of queries.
Sample below:
var results = (from o in Organizations
join m in Members on o.OrganizationId equals m.OrganizationId
group o by new { o.OrganizationId, m.MemberId } into g
select new
{
g.Key.OrganizationId,
g.Key.MemberId
})
.Take(1000);
Alas you forgot to mention your classes and the relations between the tables, so I have to make a guess.
So you have Staffs and StaffAttendances. It seems to me that there is a one-to-many relation between Staffs and StaffAttendances: every Staff has zero or more StaffAttendances; every StaffAttendance belongs to exactly one Staff, namely the Staff that foreign key StaffAttendance.StaffId refers to.
You will have classes similar to the following:
class Staff
{
public int Id {get; set;}
public string Name {get; set;}
public string Ini {get; set;}
...
// Every Staff has zero or more StaffAttendances (one-to-many)
public virtual ICollection<StaffAttendance> StaffAttendances {get; set;}
}
class StaffAttendance
{
public int Id {get; set;}
public string Mode {get; set;}
public int DayType {get; set;}
...
// every StaffAttendance belongs to exactly one Staff, using foreign key
public int StaffId {get; set;}
public virtual Staff Staff {get; set;}
}
I'm trying to figure out what your query does. It seems something like: "From every Staf, with its zero or more StaffAttendances give me the Id, the StaffName, (maybe some more Staff properties), and from all its StaffAttendances with the same Mode and DayType give me the minimum and maximum StafAttendance AccessDate"
In entity framework always use Select to query data and select only the properties that you actually plan to use. Only use Include if you plan to change the included data.
The reason for this is that Include will fetch the complete object, inclusive the properties that you won't use. If you have a database with Schools and Students, then every Student will have a foreign key to the School that he attends. Every Student on School [10] will have a foreign key SchoolId with a value [10].
If you use Include to query "School [10] with all its 2000 students" you will be transferring the value 10 more than 2000 times, while you already know this value.
Use the virtual ICollection
I think your query will be something like:
var result = dbContext.Staffs
// only if you don't want all Staffs:
.Where(staff => ...)
.Select(staff => new
{
Id = staff.Id,
Ini = staff.StaffIni,
Attendance = staff.StaffAttendances.GroupBy(attendance => new
{
Mode = attendance.Mode,
DayType = attendance.DayType,
},
// parameter resultSelector, for every [Mode, DayType] combination,
// and all StaffAttendances that have this [Mode, DayType] combination
// make one new:
(modeDayType, staffAttendancesWithThisModeDayType) => new
{
Mode = modeDayType.Mode,
DayType = modeDayType.DayType,
In = staffAttendancesWithThisModeDayType
.Select(staffAttendance => staffAttendance.AccessDate)
.Min(),
Out = staffAttendancesWithThisModeDayType
.Select(staffAttendance => staffAttendance.AccessDate)
.Max(),
},
});
Entity framework knows the relations between your tables, and translates the usage of your virtual ICollection into the proper GroupJoin.
It seems to me that you want some more properties. Since I use entity framework my SQL is a bit rusty, but I guess you'll get the gist.
I think that you don't have a Min / Max for DateTime. If you don't, you'll have to convert them to Ticks and back:
In = new DateTime(staffAttendancesWithThisModeDayType
.Select(staffAttendance => staffAttendance.AccessDate.Ticks)
.Min()),
If you are using this more often, consider to create an Extension method for this, they are one-liners, and because you reuse them it might save you a lot of time unit testing them:
public static DateTime MinAccessDate(this IQueryable<StaffAttendance> attendances)
{
return attendances.Select(attendance => attendance.AccessDate).Min();
}
public static DateTime Min(this IQueryable<DateTime> dates)
{
return new DateTime(dates.Select(date => date.Ticks).Min());
}
Usage:
In = staffAttendancesWithThisModeDayType.MinAccessDate(),
Out = staffAttendancesWithThisModeDayType.MaxAccessDate(),
In my opinion, using the virtual ICollection in a one-to-many relationship is the method that is by far the easiest to understand. Unit tests will also be easier, because your input data can come from Dictionaries or Lists instead of from real databases.
Do the GroupJoin yourself
Some people use a version of entity framework that doesn't understand the virtual ICollection, or they prefer to do the GroupJoin to fetch the Staff with their Attendances themselves. The second halve of the query will be similar:
var result = dbContext.Staffs.GroupJoin(dbContext.StaffAttendances,
staff => staff.Id, // from every Staff take the primary key
attendance => attendance.StafId, // from every StaffAttendance take the foreign key
// parameter resultSelector: for every Staff, and its zero or more StaffAttendances
// make one new
(staff, attendancesOfThisStaff) => new
{
Id = staff.Id,
Ini = staff.StaffIni,
Attendance = staff.attendancesOfThisStaff.GroupBy(attendance => new
{
Mode = attendance.Mode,
DayType = attendance.DayType,
},
// etc. see above
});

Why do I need to .Include() collections

I wrote a query which is pretty simple:
var locations = await _context.Locations
.Include(x => x.LocationsOfTheUsers)
.Include(x => x.Address)
.ThenInclude(x => x.County)
.Where(CalculateFilters(searchObj))
.ToListAsync(cancellationToken);
And everytime LocationsOfTheUsers were null so I decided to .Include(x => x.LocationsOfTheUsers) and I received results as expected but I'm not sure why do I have to include this collections since it's defined like this:
public class Location
{
public string Title { get; set; }
public long? RegionId { get; set; }
public Region Region { get; set; }
public long? AddressId { get; set; }
public Address Address { get; set; }
public long? CountyId { get; set; }
public County County { get; set; }
public ICollection<LocationsOfTheUsers> LocationsOfTheUsers { get; set; }
}
I thought this will be automatically included since it exist as ICollection in Location class.
So why is .Include() on LocationsOfTheUsers needed here?
Thanks guys
Cheers
In entity framework the non-virtual properties represent the columns of the tables, the virtual properties represent the relations between the tables (one-to-many, many-to-many, ...)
So your property should have been defined as:
public virtual ICollection<LocationsOfTheUsers> LocationsOfTheUsers { get; set; }
One of the slower parts of a database query is the transfer of the selected data from the database management system to your local process. Hence it is wise to limit the selected data to the values you actually plan to use.
If you have a one-to-many relation between Schools and Students, and you ask for School [10] you don't want automatically to fetch its 2000 Students.
Even if you would like to have "School [10] with all its Students" it would not be efficient to use Include to also fetch the Students. Every Student will have a foreign key SchoolId with a Value of [10]. If you would use Include you would transfer this foreign key 2000 times. What a waste!
When using entity framework always use Select to fetch data and select only the properties that you actually plan to use. Only use Include if you plan to change the included items.
This way you can separate your database table structure from the actual query. If your database structure changes, only the query changes, users of your query don't notice the internal changes.
Apart from better performance and more robustness against changes, readers of your code can more easily see what values are in their query.
Certainly don't use Include to save you some typing. Having to debug one error after future changes will take way more time than you will ever save by typeing include instead of Select
Finally: limit your data early in your process, so put the Where in front.
So your query should be:
var predicate = CalculateFilters(searchObj)
var queryLocations = dbContext.Locations
.Where(predicate)
.Select(location => new
{
// Select only the location properties that you plan to use
Id = location.Id,
Name = location.Name,
// Locations Of the users:
UserLocations = location.LocationsOfTheUsers
.Select(userLocation => new
{
// again: only the properties that you plan to use
Id = userLocation.Id,
...
// Not needed, you already know the value
// LocationId = userLocation.LocationId
})
.ToList(),
Address = new
{
Street = location.Address.Street,
PostCode = location.Addrress.PostCode,
...
County = location.Address.County.Name // if you only want one property
// or if you want more properties:
County = new
{
Name = location.Address.County.Name,
Abbr = location.Address.Count.Abbr,
...
}),
},
});
I thought this will be automatically included since it exist as ICollection in Location class.
Well, it's not automatically included, probably for performance reasons as the graph of related entities and their recursive child entities may be rather deep.
That's why you use eager loading to explicitly include the related entities that you want using the Include method.
The other option is to use lazy loading which means that the related entities are loaded as soon as you access the navigation property in your code, assuming some prerequisites are fulfilled and that the context is still around when this happens.
Please refer to the docs for more information.
I believe you are using EntityFrameworkCore. In EntityFramework (EF6), lazy loading is enabled by default, However, in EntityFrameworkCore, lazy loading related entities is handled by a separate package Microsoft.EntityFrameworkCore.Proxies.
To enable the behaviour you are seeking, install the above package and add the following code
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
}
After this, the related entities will be loaded without the Include call.

Not supported in LINQ to Entities

Everytime I use the Include extension, it would return an error when a value from included entity is used in the WHERE CLAUSE.
I included the System.Data.Entity which is the common answer but still have the same issue.
Model:
public partial class business_partner
{
public int id { get; set; }
public string accountid { get; set; }
}
public partial class order
{
public int id { get; set; }
public string doc_number { get; set; }
public int vendor_id { get; set; }
public int status { get; set; };
[ForeignKey("vendor_id")]
public virtual business_partner businessPartnerVendor { get; set; }
}
public IQueryable<order> GetOrder()
{
return (context.order);
}
Query:
_orderService.GetOrder()
.Include(a => a.businessPartnerVendor)
.Where(o => o.doc_number == "Order Number"
&& o.businessPartnerVendor.accountid == "TEST"
&& o.status > 2 && o.status != 9).Count() > 0
Exception:
The specified type member 'businessPartnerVendor' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Alas you forgot to write your requirement. Your code doesn't do what you want, so I might come to the incorrect conclusion, but looking at your code, it seems that you want the following:
Tell me whether there are Orders, that
- have a value of DocNumber that equals "Order_Number",
- AND that are orders of a BusinessPartnerVendor with a value of AccountId equal to "TEST",
- AND have a value of Status which is more than 2 and not equal to 9.
The part "Tell me whether there are Orders that", was deducted by the fact that you only want to know whether Count() > 0
Your Count would have joined all elements, included all columns of BusinessPartnerVendor, removed all rows that didn't match your Where, and counted how many joined items were left. That integer value would be transferred, after which your process would check whether the value is larger than zero.
One of the slower parts of a database query is the transport of the selected data to from the Database Management System to your local process. Hence it is wise to limit the amount of transferred data.
Quite often I see people using Include to get the items that are stored in a different table (quite often a one-to-many). This will select the complete row. From the businessPartnerVendor, you only want to use property AccountId. So why select the complete object?
In entity framework use Select to select properties you want to query. Only use Include if you want to update the fetched data.
bool areTestOrdersAvailable = orderService.GetOrder()
.Where(order => order.doc_number == "Order Number"
&& order.businessPartnerVendor.accountid == "TEST"
&& order.status > 2 && order.status != 9)
.Any();
Because of the virtual keyword in your classes (ans maybe some fluent API), entity framework knows about the one-to-many relation and will perform the correct join for you. It will only use SQL "TAKE 1" to detect whether there are any elements. Only one Boolean is transferred
Some Advices about entity framework
It is good practice to stick as much as possible to the entity framework code first conventions The more you do this, the less Attributes and Fluent API is needed. There will also be less discrepancy between the way Microsoft's usage of identifiers for classes, fields, properties, methods, etc and yours.
In entity framework, all columns of a table are represented by non-virtual properties, the virtual properties represent the relations between tables (one-to-many, many-to-many, ...)
My advice would be: add the foreign keys to your classes, and stick to one identifier to describe one row in your tables.
So decide whether to use business_partner or BusinessPartnerVendor if they are in fact the same kind of thing
Add the foreign key:
// Every Order is the Order of exactly one BusinessPartner, using foreign key (one-to-many)
public int BusinessPartnerId {get; set;}
public virtual BusinessPartner BusinessPartner {get; set;}
This has the advantage, that if you want to select the Ids of all BusinessPartners that have one or more Orders that ..., you don't have to perform a join:
var businessPartnerIds = myDbContext.Orders
.Where(order => ...)
.Select(order => order.BusinessPartnerId)
.Distinct();
Only one database table will be accessed

Lambda Select Top 1 item on underlying List

I have 2 tables with relation
Customer
Id, Nbr, Name
Assignments
Id, CustomerId, Location, AssigmentTime
There is a relation on Customer.Id = Assigments.CustomerId
Every Customer can have lot of assignments, but I am only interested in the last Assignment according to DateTime field AssigmentTime
In SQL it should be a query like:
Select Top 1 From Customer c
Inner Join Assigments a On c.Id = a.CustomerId
Where c.Nbr = 1234
Order By AssigmentTime Desc
I have a problem to construct proper Lambda query.
This code works, but it’s not very effective:
var customerNbr = 1234:
var cst = context.Customers.FirstOrDefault(x => x.Nbr == customerNbr);
if (cst != null && cst. Assigments.Count > 1)
{
cst. Assigments = new List<Assigments>
{
cst.Assigments.OrderByDescending(x => x.AssigmentTime).FirstOrDefault()
};
}
How can I get Customer with just 1 top Assigments in Customer.Assigments List property?
For example:
var lastAssignment = customers.Where(x => x.Nbr == customerNbr)
.SelectMany(x => x.Assignments)
.OrderByDescending(x => x.AssignTime)
.FirstOrDefault();
If you have set-up your entity framework according to the proper coding conventions you'll have designed the one-to-many relation as follows:
class Customer
{
public int Id {get; set;} // primary key
// a Customer has zero or more Assignments
public virtual ICollection<Assignment> Assignments {get; set;}
public int Nbr {get; set;}
... // other properties
}
class Assignment
{
public int Id {get; set;} // primary key
// every Assignment belongs to one Customer via foreign key
public int CustomerId {get; set;}
public virtual Customer Customer {get; set;}
public DateTime AssignmentTime {get; set;}
... // other properties
}
public MyDbContext : DbContext
{
public DbSet<Customer> Customers {get; set;}
public DbSet<Assignment> Assignments {get; set;}
}
If you've set-up the one-to-many like this, then this is all entity framework needs to know that you designed a one-to-many relationship. If you didn't want to follow the naming conventions, you probably have used fluent API or attributes to configure the one-to-many.
Get the Customer with Nbr = 1234 with his last (newest) Assignment:
using (var dbContext = new MyDbContext())
{
var result = dbContext.Customers
.Where(customer => customer.Nbr == 1234)
.Select(customer => new
{
// select the customer properties you will use, for instance
CustomerId = customer.Id,
CustomerName = customer.Name,
// you only want the newest assignment:
NewestAssignment = customer.Assignments
.OrderByDescending(assignment => assignment.AssignmentTime)
.Select(assignment => new
{ // take only the Assignment properties you will use:
Location = assignment.Location,
AssignmentTime = assignment.AssignmentTime,
}
.FirstOrDefault(),
});
}
}
If you are certain there is at utmost one customer with Nbr = 1234, you can end with SingleOrDefault; otherwise your result will be the sequence of Customers with this Nbr.
Each customer will only have the customer properties you will use, and the properties of the newest Assignment you will use. Efficient!
Thank you for your suggestion Harald. I was on to same thig, but I found anonymous object to be a bit bloated. In my case I use EF.Reverse.POCO Generator, so every object is strictly mapped to DB. Customer and Assignments are in reality something else – tables with lot of columns. I can’t have anonymous object as a return from this function.
I could still do something like this:
using (var dbContext = new MyDbContext())
{
var result = dbContext.Customers
.Where(customer => customer.Nbr == 1234)
.Select(customer => new Customer
{
// select the customer properties you will use, for instance
Id = customer.Id,
Nbr = customer.Nbr,
Name = customer.Name,
//…and lot of other property mapping
// you only want the newest assignment:
Assignments = new Collection<Assignments>
{
customer.Assignments.OrderByDescending(assignment => assignment.AssignmentTime)
.FirstOrDefault()
}
});
}
}
The anonymous Customer generations will result in lot of property mapping. That’s the minor issue.
Even if I skip Assignments property, this solution with typed object in Select generates an exception inside the result:
Message = "The entity or complex type 'MyNamespace.Customer' cannot be constructed in a LINQ to Entities query."
If I use anonymous object the same code works fine, but as I wrote above – I need typed objects as return.

Categories

Resources