Entity Framework - Pulling back related data - c#

I have an ASP.net MVC controller action that is retrieving a list of items from my entity model. For this entity, there are a few properties that aren't in the entity itself. I created a partial class to add these properties:
public partial class Person
{
public int Extra
{
get
{
using( var ctx = new DBEntities() )
{
return ctx.OtherTable.Count(p => p.PersonID == this.PersonID);
}
}
}
}
As you can see, the property that I need access to comes from another table. In my MVC page...I need to return a large number of people (100+ per page). In order to display this Extra field, each Person entity is going to be hitting the database separately...which has been extremely inefficient. I have one query to return all the people, and then for each person it has a query for each property I have like this. This can end up being 300 calls to the database, which is taking a long time to execute.
What is a better way to do this? I would ideally like to execute one query that returns all the People and the extra data, but I would also like the extra data to be part of the Person entity, even if it is in a separate table in the database.
Update
To add a little more context from the comments.
I am returning the People from a repository class. I was told in another question that the repository should only be dealing with the entities themselves. So the code that retrieves the people is like:
class PersonRepository
{
public IQueryable<Person> GetPeople() {
return from p in db.People
where p ...
select p;
}
}
I don't really have the option to join in that case.

You could do joins:
var result =
from p in ctx.Persons
from a in ctx.OtherTable
where p.PersonID == personID
select new SomeViewModel
{
Name = p.Person.Name,
OtherTableValue = a.OtherValue
};

I'm not sure about how your database design is done. But why can't you join the data from the two related tables and hit the data once rather then multiple times?
Even if that is slow for you, you can also cache this data and be able to access it during the lifetime of the session.

Related

With Entity Framework how to create nested objects without one massive query resultset or hundreds of small queries?

I'm using EF to populate objects which I then interact with in my business layer code. The objects have several levels but let's first simplify it to a typical master-detail example with Order and OrderLine.
Let's say I need to retrieve 50 orders each of which has about 100 order lines, and I need all that data. What's the most efficient way of doing this in EF?
If I do it like this:
var orders = context.Orders.Where(o => o.Status == "Whatever")
.Include(order => order.OrderLines)
.ToList();
Then it'll run a single query that pulls back 50x100 rows, then it cleverly assembles that into 50 Order objects, each with their own OrderLine objects.
If instead I don't do the Include() and I later iterate over each Order and its OrderLines, then it'll issue 50 separate queries to get the OrderLine data, i.e. one for each Order.
So far the .Include() seems great: I pull back a bit more data than needed from SQL but that's much better than issuing 50 extra queries. But is there a way I can choose to issue 2 queries, one to get Order and one to get OrderLine, and have EF connect the objects automatically?
A more realistic scenario where I want this is if the data is more complex. Let's say I want objects like this (where Product is the thing being bought in the OrderLine and ProductPart is a number of bits used to make the Product):
- Order
- OrderLine (avg 100 per Order)
- Product (1 per OrderLine)
- ProductPart (avg 20 per Product)
Now if I do a .Include() with ProductPart, it'll make the query results much bigger. Or if I don't Include() then it'll issue separate queries for each Product.
Is there a third way where I can get all the Order, OrderLine and Product data in one query and all the ProductPart data in another query, and magically have EF connect the objects up for me?
UPDATE:
I just read about AsSplitQuery() which seems to be what I'm looking for but is only available in EF Core 5 which isn't stable till Nov 2020 (?). I'm looking for a way to achieve this in EF6.
A bit more research and I found https://entityframework.net/improve-ef-include-performance which suggests two approaches when you have multiple object types coming off the parent object:
Execute multiple EF commands to pull back the same list of parent objects, but each time Include()-ing different child object types. EF will hook up the related objects it's already pulled from the db, apparently.
Use the EF+ library which seems it can do something like AsSplitQuery()
I'm not sure if this works with my case where there's more levels of nesting rather than just different types of objects off the parent. My guess is yes, if I'm clever about it. Any tips?
Something like this might destructure your object results a bit, but should perform two separate queries.
var ordersQuery = context.Orders.Where(o => o.Status == "Whatever");
var orderLineGroups = ordersQuery
.SelectMany(o => o.OrderLines)
.ToLookup(l => l.OrderID); // <- Not sure what your FK name is
var orders = ordersQuery.Select(o => new {
Order = o,
OrderLines = orderLineGroups[o.Id]
}).ToList();
Not sure that it will work in EF6 but you can try rely on tracking and relationship fix up. Next one works for me in the latest EF Core:
public class Make
{
public int MakeId { get; set; }
public string Name { get; set; }
public ICollection<Model> Models { get; set; }
public Make()
{
Models = new HashSet<Model>();
}
}
public class Model
{
public int ModelId { get; set; }
public string Name { get; set; }
public Make Make { get; set; }
public int MakeId { get; set; }
}
ctx.Makes.Where(m => m.Name=="3").SelectMany(m=> m.Models).ToList();
ctx.Makes.Where(m => m.Name=="3").ToList(); // makes have models filled in
If you are issuing tracking queries, then EF will fixup all the references while the results are being returned. You can also use the Load method to load the results & fixup references without constructing a list.
For example;
var orders = context.Orders
.Where(o => o.Status == "Whatever")
.ToList();
context.OrderLines
.Where(l => l.Order.Status == "Whatever")
.Include(l => l.Product) // maybe this is a reasonable tradeoff?
.Load();
context.ProductPart
.Where(p => p.Product.OrderLines.Any(l => l.Order.Status == "Whatever"))
.Load();

Entity Framework Count with a filter on field really slow - large count

When I am running a Model Mapping, one company has a lot of members, like 405,000 members.
viewModel.EmployeeCount = company.MembershipUser.Count(x => x.Deleted == false);
When I run the SQL query, it takes a few milliseconds. In ASP.NET MVC, EF6 C# this can take up to 10 minutes for one list view controller hit. Thoughts?
Company is my Domain Model Entity, and MembershipUser is a public virtual virtual (FK) using entity framework 6, not C#6
When I'm in my CompanyController (MVC) and I ask for a company list, I get a list without the company count included. When I do a viewModelMapping to my Model to prep to pass to the view, I need to add the count, and do not have access to the context or DB, etc.
// Redisplay list of companies
var viewModel = CrmViewModelMapping.CompanyListToCompanyViewModel(pagedCompanyList);
CompanyListToCompanyViewModel maps the list of companies to the list of my ViewModel and does the count (MembershipUsers) there.
I also tried adding the count property to the company DomainModel such as:
public int EmployeeCount
{
get
{
// return MembershipUser.Where(x => x.Deleted == false).Count();
return MembershipUser.Count(x => x.Deleted == false);
}
}
But it also takes a long time on companies with a lot of Employees.
It's almost like I want this to be my SQL Query:
Select *, (SELECT count(EmployeeID) as Count WHERE Employee.CompanyID = CompanyID) as employeeCount from Company
But early on I just assumed I could let EF lazy loading and subQueries do the work. but the overhead on large counts is killing me. On small datasets I see no real difference, but once the counts get large my site is unsusable.
When you are accessing the navigation property and using the count method, you are materializing all the MembershipUser table and doing the filter in C#.
There are three operations in this command: The C# go to the database and execute the query, transform the query result in C# object list (materialize) and execute the filter (x => x.Deleted == false) in this list.
To solve this problem you can do the filter in the MembershipUser DbSet:
Db.MembershipUser.Count(x => x.Deleted == false && companyId == company.Id);
Doing the query using the DbSet, the filter will be done in database without materialize all 405000 rows.

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.

How to update related data using entity framework 6

I have an entity Person, which has a related entity Hobby. This is a many to many relationship where a person can have several hobbies, and a hobby can be associated with many people. I want to create a ViewModel that allows new hobbies to be added and/or existing hobbies to be removed from a given Person entity.
The code below works, but I imagine it is rather inefficient.
var newHobbies = new List<Hobby>();
foreach (Hobby hobby in vm.Hobbies)
{
var originalHobby = db.Hobbies.Find(hobby.HobbyID);
newHobbies.Add(originalHobby);
}
originalPerson.Hobbies = newHobbies;
I prefer to do something like this:
var newHobbies = db.Hobbies.Where(x => vm.Hobbies.All(y => x.HobbyID == y.HobbyID)).ToList();
originalPerson.Hobbies = newHobbies;
But I get an error:
Only primitive types or enumeration types are supported in this
context.
How can I update related data without going to the database multiple times?
To avoid that exception you can select first the Ids from the vm.Hobbies collection and after that filter the hobbies you need:
var Ids=vm.Hobbies.Select(x=>x.HobbyID);
var newHobbies = db.Hobbies.Where(x => Ids.Contains(x.HobbyID)).ToList();
// another option could be: db.Hobbies.Where(x => Ids.Any(id=>id==x.HobbyID)).ToList();
originalPerson.Hobbies = newHobbies;
The message says that you can't use vm.Hobbies inside a LINQ-to-Entities query, only primitive types. This is what you can do:
var hobbyIds = vm.Hobbies.Select(h => h.HobbyId).ToList();
var newHobbies = db.Hobbies.Where(x => hobbyIds.Contains(x.HobbyID)).ToList();
The overall construction would be like such:
Your view takes the view model which displays the list of hobbies
You have an action to add a new Hobby passing a Hobby entity back as part of the action
You simply make a call back to the database to create the new association between the Person and Hobby
Add the Hobby entity to the collection in Person
Return passing the view model back to the view
In general EF and MVC are much easier to work with when you try to interact with your entities as whole entities and don't try to micromanage yourself things such as IDs, entity hydration, and so on.
You could join by ID instead of fetching data from the DB:
originalPerson.Hobbies = db.Hobbies.Join(vm.Hobbies, h => h.ID,
v => v.HobbyID, (h, v) => h).ToList();

Best way of handling views

I want to start that new project with the best practice possible. I have a lot of tables that are linked together. Example:
Person
- ID
- FirstName
- LastName
User
- ID
- PersonID
- AddressID
Address
- ID
- Line1
- Line2
Obviously, I will have to use a UserView view to be efficient in the application. The view will bring these three tables together.
Now, when someone makes a change to a user, I will have to query all three tables individually, make the changes and then update.
private void updateUser(UserView userView)
{
using (var context = getNewContext()
{
var person = context.Person.first(c => c.ID == userView.PersonID)
person.FirstName = userView.FirstName;
person.LastName = userView.LastName;
context.SaveChanges();
var user = context.User.first(c => c.ID == userView.UserID)
//and so on for the 3 tables
}
}
There has to be a more efficient way of updating the tables! Do you know any trick?
I assume you're using the entity framework. If you've got navigation properties you can eagerly load your object graph in one trip to the database, update the fields and then save it.
var user = context.User.Include("Person").Include("Address").First(c => c.ID == userView.UserID);
user.Person.FirstName = userView.FirstName;
user.Person.LastName = userView.LastName;
user.Address... // etc.
If you're using Linq2Sql, you've got to use DataLoadOptions. There's a blog post explaining the difference between EF and Linq2Sql.

Categories

Resources