Linq to entities and Hierarchical table - c#

I have a table with id and parentId columns the nesting level is just 1(for now).
Right now I load items like this:
using (KEntities ctx = new KEntities())
{
ctx.KSet.MergeOption = MergeOption.NoTracking;
var items = (from c in ctx.KSet
where c.ParentId == 0
select new
{
Title = c.Title,
Id = c.Id,
Subs = ctx.KSet.Where(o => o.ParentId == c.Id)
}).ToList();
}
The other option that I can choose is to set self-reference on the table, so the entity will expose self-navigation properties and then I can use Load() to load the children (lazy loading?).
Which approach is preferred and why?

IMHO, I prefer what you have done as in your example. I like to call the .ToList() cause then I know at that moment I have the data in memory, and don't have to worry about some of the problems you can have with Lazy loading.
"it leaks persistent storage access to different tiers via the lazy loadable associations."
taken from link

The valid option is exposing navigation property Children and calling:
var items = ctx.KSet.Include("Children").Where(c => c.ParentId == 0);
This will allow you working directly with KSet entities. Projection will create a new type. In case of exposing navigation property you also have the choice of using eager loading (as shown in the example) or explicit / lazy loading if you want to. Projection make sense only if you want to filter or sort children.

Related

Entity Framework Core 6 - Select on ICollection Navigation Property loading more than specified columns

I am currently using EF Core 6 (w/ lazy loading) to access my DB.
When I access my desired data like this:
var depCount = admin.Departments.Count(d => !d.Deleted)
SQL Server Profiler shows me this:
exec sp_executesql N'SELECT [t].[DepId], [t].[BaseDepId], [t].[CreatedTimeStamp], [t].[CustCode], [t].[Deleted], [t].[DeletedTimeStamp], [t].[DepName], [t].[ForSingleSurvey], [t].[Level], [t].[SendInvitationMails], [a].[AdminId], [t].[AdministratorsAdminId], [t].[DepartmentsDepId], [t0].[AdministratorsAdminId], [t0].[DepartmentsDepId], [t0].[AdminId], [t0].[AdminEmail], [t0].[AdminPwdHash], [t0].[AuthToken], [t0].[CreatedTimeStamp], [t0].[CustCode], [t0].[CycleId], [t0].[EmailConfirmed], [t0].[FirstName], [t0].[LastName], [t0].[LastTokenGenerated], [t0].[OnlyManaging]
FROM [Administrators] AS [a]
INNER JOIN (
SELECT [d].[DepId], [d].[BaseDepId], [d].[CreatedTimeStamp], [d].[CustCode], [d].[Deleted], [d].[DeletedTimeStamp], [d].[DepName], [d].[ForSingleSurvey], [d].[Level], [d].[SendInvitationMails], [a0].[AdministratorsAdminId], [a0].[DepartmentsDepId]
FROM [AdministratorDepartment] AS [a0]
INNER JOIN [Departments] AS [d] ON [a0].[DepartmentsDepId] = [d].[DepId]
) AS [t] ON [a].[AdminId] = [t].[AdministratorsAdminId]
LEFT JOIN (
SELECT [a1].[AdministratorsAdminId], [a1].[DepartmentsDepId], [a2].[AdminId], [a2].[AdminEmail], [a2].[AdminPwdHash], [a2].[AuthToken], [a2].[CreatedTimeStamp], [a2].[CustCode], [a2].[CycleId], [a2].[EmailConfirmed], [a2].[FirstName], [a2].[LastName], [a2].[LastTokenGenerated], [a2].[OnlyManaging]
FROM [AdministratorDepartment] AS [a1]
INNER JOIN [Administrators] AS [a2] ON [a1].[AdministratorsAdminId] = [a2].[AdminId]
WHERE [a2].[AdminId] = #__p_0
) AS [t0] ON [t].[DepId] = [t0].[DepartmentsDepId]
WHERE [a].[AdminId] = #__p_0
ORDER BY [a].[AdminId], [t].[AdministratorsAdminId], [t].[DepartmentsDepId], [t].[DepId], [t0].[AdministratorsAdminId], [t0].[DepartmentsDepId]',N'#__p_0 int',#__p_0=122
which obviously is very inefficient and way too much overhead.
However, when I access my desired data like this:
var depCount = await context.Departments.CountAsync(d => d.Admins.Any(a => a.AdminId == admin.AdminId) && !d.Deleted)
the Profiler shows me the following statement:
exec sp_executesql N'SELECT COUNT(*)
FROM [Departments] AS [d]
WHERE EXISTS (
SELECT 1
FROM [AdministratorDepartment] AS [a]
INNER JOIN [Administrators] AS [a0] ON [a].[AdministratorsAdminId] = [a0].[AdminId]
WHERE ([d].[DepId] = [a].[DepartmentsDepId]) AND ([a0].[AdminId] = #__a_AdminId_0)) AND ([d].[Deleted] = CAST(0 AS bit))',N'#__a_AdminId_0 int',#__a_AdminId_0=113
which is what I would want.
Does anybody know if I can produce that behavior with the first accessing method (via the Navigation Property)?
Since this would be way easier to code...
Thank you in advance!
The first example will access the collection if eager loaded, otherwise if you have lazy loading enabled then the proxy will result in a DB hit to load the related Departments. Based on that you're seeing the query run then that indicates lazy loading is enabled and running. Lazy loading incurs a performance hit, but serves as a safety net to ensure that if data is available, it can be loaded when accessed.
When loading entities you need to plan ahead a bit and either ensure data you need will be eager loaded with the entity(ies) or that you are projecting the data down to include all of the details you will need.
For example, eager loading:
var admin = await context.Admins
.Include(x => x.Departments)
.SingleOrDefaultAsync(x => x.AdminId == adminId);
// later...
var departmentCount = admin.Departments.Count();
This avoids the extra DB hit as the departments would have been loaded when the Admin was. The cost to consider with eager loading is that you are still loading a lot of data (everything about admin and all of their departments and any other related data you eager load) which may not be necessary. In the above case we just want a count. The issue I often find with teams that insist on loading entities and want to avoid lazy loading is that they default to eager loading everything. Still, when loading single entities, eager loading related data is generally Ok where it is useful for that data to be loaded.
When reading data especially, it can be much better to consider using projection solutions to fetch the data you want to consume. For instance take a situation where you want to search for Admins and include information like their department count. Using eager loading you'd have something like:
var admins = await context.Admins
.Include(x => x.Departments)
.Where(x => *some condition*)
.Skip(pageNumber * pageSize)
.Take(pageSize)
.ToListAsync();
Even best case that we use server-side pagination to control the amount of data included, this still loads a page of admins and all departments for each. If there are other related entities this can balloon out the amount of data loaded fairly quickly. EF can end up generating things like Cartesian products between related tables which it will chew through to produce the entity graph, but this will still take up memory and time.
With projection we could create something like:
[Serializable]
public class AdminSummaryViewModel
{
public int AdminId { get; set; }
public string Name { get; set; }
// Additional details we want to show....
public int DepartmentCount { get; set; }
}
Then when we go to get the data:
var admins = await context.Admins
.Where(x => *some condition*)
.Select(x => new AdminSummaryViewModel
{
AdminId = x.AdminId,
Name = x.Name,
// ...
DepartmentCount = x.Departments.Count()
})
.Skip(pageNumber * pageSize)
.Take(pageSize)
.ToListAsync();
This generates an SQL statement that will only query the details we need from the relevant tables, including condensing down the requested department count. This reduces the total amount of data passed over the wire and time needed for EF to produce the view models desired. Automapper has a ProjectTo extension method for IQueryable which can populate a ViewModel without having to write out that .Select() block.
admin.Departments.Count(d => !d.Deleted) is lazy loading the entire Departments collection, then calling Enumerable.Count to give the answer.
Without using any helper methods, you can get the answer this way;
public int DepartmentCount (DbContext context, Admin admin)
{
var navigation = context
.Entry(admin)
.Navigation(nameof(Admin.Departments));
// if it's already in memory, just use that
if (navigation.IsLoaded)
return admin.Departments.Count(d => !d.Deleted);
// otherwise query the database
// EF Core can generate the query for you, equivalent to supplying FK parameters;
// context.Departments.Where(d => d.AdminId == admin.Id)
var query = (IQueryable<Department>)navigation.Query();
return query.Count(d => !d.Deleted);
}

Access Object through foreign key c#

I have the following code and would like to know if there is a way to refactor in order to remove duplicated logic.
This results current user with eager loading.
var currentEmployee = RosterContext.Employees
.Where(e => e.User.Id == id)
.Include(e => e.Job.Department).FirstOrDefault();
.
var job = RosterContext.Employees.Where(e=>e.Job.Department.Id == currentEmployee.Job.DepartmentId).ToList();
I created another same context which compares the first line of code to result all employee names who work in same department. My question is, as I am using two linq expression that uses the same context (Employees) am i able to combine both linq queries into one?
It may become a long linq expression but it should serve on getting the current user object followed by comparing user object to get all employees that share the same department id?
It makes sense to try an ORM framework, such as Entity Framework or NHibernate.
ORM framewok will model database FK relationship as a scalar property on one side and vector property (collection) on the other side of the relationship.
For instance Department would have a collection property Jobs, and a Job entity would have a scalar Department property.
DB queries with joins on FK become just dependency property navigation, for example - to access the list of employees in current department you would just return something like employee.Department.Employees - that is, assuming your entities are all loaded (which is rather simple to achieve in EF, using include statement)
In Entity Framework you have the using clause to attach children. So for example in pure EF you could do:
var department = context.Department.Include(d => d.Jobs).First(d => d.DepartmentId == departmentId);
https://msdn.microsoft.com/en-us/library/gg671236%28v=vs.103%29.aspx#Anchor_1
With a repository, you may need to do something like this:
EF Including Other Entities (Generic Repository pattern)
EF Code First supports relationships out of the box. You can either use the conventions or explicitly specify the relationship (for example, if the foreign key property is named something weird). See here for example: https://msdn.microsoft.com/en-us/data/hh134698.aspx
When you've configured your models right, you should be able to access department like so:
var currentUser = _unitOfWork.Employee.GetEmployeeByID(loggedInUser.GetUser(user).Id);
var job = currentUser.Job;
var department = job.Department;
// or
var department = _unitOfWork.Employee.GetEmployeeByID(loggedInUser.GetUser(user).Id).Job.Department;
To show all employees that work in the same department:
var coworkers = department.Jobs.SelectMany(j => j.Employees);
Update
To use eager loading with a single repository class (you shouldn't need multiple repository classes in this instance, and therefore don't need to use Unit of Work):
public class EmployeeRepository {
private readonly MyContext _context = new MyContext(); // Or whatever...
public IList<Employee> GetCoworkers(int userId) {
var currentEmployee = _context.Employees
.Where(e => e.UserId == userId)
.Include(e => e.Job.Department) // Use eager loading; this will fetch Job and Department rows for this user
.FirstOrDefault();
var department = currentEmployee.Job.Department;
var coworkers = department.Jobs.SelectMany(j => j.Employees);
return coworkers;
}
}
And call it like so...
var repo = new EmployeeRepository();
var coworkers = repo.GetCoworkers(loggedInUser.GetUser(user).Id);
You probably would be able to make the repository query more efficient by selecting the job and department of the current user (like I've done) and then the related jobs and employees when coming back the other way. I'll leave that up to you.

LINQ: Is there a way to combine these queries into one?

I have a database that contains 3 tables:
Phones
PhoneListings
PhoneConditions
PhoneListings has a FK from the Phones table(PhoneID), and a FK from the Phone Conditions table(conditionID)
I am working on a function that adds a Phone Listing to the user's cart, and returns all of the necessary information for the user. The phone make and model are contained in the PHONES table, and the details about the Condition are contained in the PhoneConditions table.
Currently I am using 3 queries to obtain all the neccesary information. Is there a way to combine all of this into one query?
public ActionResult phoneAdd(int listingID, int qty)
{
ShoppingBasket myBasket = new ShoppingBasket();
string BasketID = myBasket.GetBasketID(this.HttpContext);
var PhoneListingQuery = (from x in myDB.phoneListings
where x.phonelistingID == listingID
select x).Single();
var PhoneCondition = myDB.phoneConditions
.Where(x => x.conditionID == PhoneListingQuery.phonelistingID).Single();
var PhoneDataQuery = (from ph in myDB.Phones
where ph.PhoneID == PhoneListingQuery.phonePageID
select ph).SingleOrDefault();
}
You could project the result into an anonymous class, or a Tuple, or even a custom shaped entity in a single line, however the overall database performance might not be any better:
var phoneObjects = myDB.phoneListings
.Where(pl => pl.phonelistingID == listingID)
.Select(pl => new
{
PhoneListingQuery = pl,
PhoneCondition = myDB.phoneConditions
.Single(pc => pc.conditionID == pl.phonelistingID),
PhoneDataQuery = myDB.Phones
.SingleOrDefault(ph => ph.PhoneID == pl.phonePageID)
})
.Single();
// Access phoneObjects.PhoneListingQuery / PhoneCondition / PhoneDataQuery as needed
There are also slightly more compact overloads of the LINQ Single and SingleOrDefault extensions which take a predicate as a parameter, which will help reduce the code slightly.
Edit
As an alternative to multiple retrievals from the ORM DbContext, or doing explicit manual Joins, if you set up navigation relationships between entities in your model via the navigable join keys (usually the Foreign Keys in the underlying tables), you can specify the depth of fetch with an eager load, using Include:
var phoneListingWithAssociations = myDB.phoneListings
.Include(pl => pl.PhoneConditions)
.Include(pl => pl.Phones)
.Single(pl => pl.phonelistingID == listingID);
Which will return the entity graph in phoneListingWithAssociations
(Assuming foreign keys PhoneListing.phonePageID => Phones.phoneId and
PhoneCondition.conditionID => PhoneListing.phonelistingID)
You should be able to pull it all in one query with join, I think.
But as pointed out you might not achieve alot of speed from this, as you are just picking the first match and then moving on, not really doing any inner comparisons.
If you know there exist atleast one data point in each table then you might aswell pull all at the same time. if not then waiting with the "sub queries" is nice as done by StuartLC.
var Phone = (from a in myDB.phoneListings
join b in myDB.phoneConditions on a.phonelistingID equals b.conditionID
join c in ph in myDB.Phones on a.phonePageID equals c.PhoneID
where
a.phonelistingID == listingID
select new {
Listing = a,
Condition = b,
Data = c
}).FirstOrDefault();
FirstOrDefault because single throws error if there exists more than one element.

Use LINQ-to-SQL to return an object that has child objects filtered

I have a MembershipGroups table that is associated with a child Members table. The Members table has a Status column which can be set to Active or Inactive.
I want to select all MembershipGroups and only their active Members
As an example,
MembershipGroups
ID----Title
1-----Group #1
2-----Group #2
Members
MembershipGroupID-Name--Status
1-------------------------John----Active
1-------------------------Sally----Inactive
1-------------------------David---Inactive
I'm trying to create a query that looks something like the following (which doesn't currently work):
var query = from mg in db.MembershipGroups
where mg.Members.Status = "Active"
select mg
The result for this example should return a MembershipGroup of ID#1 with only one child Member entity
How can use LINQ-to-SQL to select a parent object that filters on child objects? If I were using straight T-SQL then this would be a simple join with a where clause but it seems to be much more difficult to do using LINQ-to-SQL.
Edit - Updated answer to return the MemberShipGroup object
var query = (from mg in db.MembershipGroups
join m in db.Members.Where(mem => mem.Status == "Active")
on mg.ID equals m.MembershipGroups into members
select new
{
MembershipGroup = mg,
Members = members
}).AsEnumerable()
.Select(m => new MembershipGroup
{
ID = m.MembershipGroup.ID,
Title = m.MembershipGroup.Title,
Members = m.Members
});
In LINQ to SQL, you can use the AssociateWith method on the DataLoadOptions to set your child filter at the context level.
DataLoadOptions opt = new DataLoadOptions();
opt.AssociateWith<Member>(m => m.Status == "Active");
db.LoadOptions = opt;
With this in place, you can simply return your member groups (or filter them for the active ones using where mg.Any(group => group.Members.Status == "Active"). Then when you try to drill into the Members of that group, only the Active ones will be returned due to the LoadOptions.
See also http://msdn.microsoft.com/en-us/library/system.data.linq.dataloadoptions.associatewith.aspx .
One word of warning, once you set the LoadOptions on a context instance, you can not change it. You may want to use a customized context to use this option.
As an alternative, you could use LINQ to SQL's inheritance model to create an ActiveMember type using the Status column as your discriminator and then create an association between the MemberGroups and ActiveMembers types. This would be the approach you would need to use to model this with the Entity Framework if you though about going that route as well as EF doesn't support the concept of the LoadOptions.
Make sure you are including the child objects you are trying to filter on, inside the query.
E.g.
var query = db.MembershipGroups
.Include("Members")
.Where(m => m.Members.Status == "Active");

Linking Multiple Tables in LINQ to SQL

I would like to get the list of albums (Distinct) which was sung by the artistId=1
I am very new to LINQ to SQL and do not know how to join multiple tables. Please see the database diagram below:
alt text http://a.imageshack.us/img155/8572/13690801.jpg
SingBy is the middle table between Track and Artist.
How could I achieve this?
var albums = from singer in artist
from sb in singby
from t in track
from a in album
where singer.artistId == 1 &&
sb.artistId == 1 &&
sb.trackId == t.trackId &&
a.albumId == track.albumId
select a;
I'm sure there must be a better way. You should look into creating Navigation Properties on your entities. Navigation Properties are like foreign keys.
Edit - corrected to get albums, not artists.
Now, I wrote the codes like the following and it works.
var albums = (from a in db.artists
where a.artistId == 1
join sb in db.singbies on a equals sb.artist
join t in db.tracks on sb.track equals t
join al in db.albums on t.album equals al
select al).Distinct();
return albums.ToList() as List<album>;
I tested the Chad's version and it works too. I would like to know which way is better and good for query optimization? Thanks all.
If you have all the foreign key relationship defined, you should be able to issue call like below:
dc.GetTable<Album>().Where(a => a.Track.Singby.ArtistId == 1).ToList();
This is relying on Linq to perform lazy load for Track and Singby automatically when required. Obviously this is not optimal to use when you have a large set of data in the db and performance is critical. You can chain the query with GroupBy or Distinct operation to return only the distinct set such as
dc.GetTable<Album>().Where(a => a.Track.Singby.ArtistId == 1).Distinct().ToList();
I would like to get the list of albums
(Distinct) which was sung by the
artistId=1
DBDataContext = new DBDataContext();
album[] = db.artists.Where(a => a.artistId == 1) /* Your artist */
.SelectMany(a => a.singbies) /* Check if `singby` converted to `singbies` */
.Select(sb => sb.track) /* The tracks */
.Select(t => t.album) /* The albums */
.GroupBy(al => al.albumId) /* Group by id */ /* "Distinct" for objects */
.Select(alG => alG.First()) /* Select first of each group */
.ToArray();
IEnumerable<Album> query =
from album in myDC.Albums
let artists =
from track in album.Tracks
from singBy in track.SingBys
select singBy.Artist
where artists.Any(artist => artist.ArtistId == 1)
select album;
List<int> Ids = dc.Albums.Where(a => a.Track.Singby.ArtistId == 1).Select(a=> a.albumId).Distinct().ToList();
List<Album> distinctAlbums = dc.Albums.Where(a => distinctAlbumIds.Contains(a.albumId)).ToList();
Hey TTCG, above is the simplest way to do it. This is because doing a Distinct on a List of objects won't do it based on the albumId.
Either you do it in two steps as above, or, you write your own Album Comparer which specifies uniqueness based on AlbumId and pass it to the Distinct call on a List.
NOTE:
The above will only work if you've defined the constraints in your DBML, but better still in your DB.
For best practices, always define your relationships IN THE DATABASE when using Linq to SQL, as Linq to SQL is not like EF, or NHibernate, in that is does not "abstract" your db, it simply reflects it. It's a tool for Data Driven Design, not Domain Driven, so define the relationships in the db.

Categories

Resources