I have following issue - I'm using generic repository to detach and reattach entity on update operation to prevent accessing it from two different objects.
This is how Update operation looks like:
public void Update(TEntity entityToUpdate)
{
if (this.context.Entry(entityToUpdate).State != EntityState.Detached)
{
this.context.Entry(entityToUpdate).State = EntityState.Detached;
}
this.context.Entry(entityToUpdate).State = EntityState.Modified;
}
When using two entities lets say like user and course
public class User
{
public Guid UserId { get; set; }
public string Email { get; set; }
public ICollection<Course> Courses{ get; set; }
}
}
and Course
public class Course
{
public string Name { get; set; }
public Guid CourseId { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class CourseDto
{
public string Name { get; set; }
public Guid CourseId { get; set; }
public virtual ICollection<Guid> Users { get; set; }
}
and I try to update course's users with following code:
public async Task<Course> Update(Guid existingCourseId, CourseDto courseModel)
{
var course = await course.Repository.Query(true)
.Include(c=> c.Users)
.Where(e => e.CourseId == existingCourseId )
.FirstOrDefaultAsync();
if (course == null)
{
return null;
}
course.Users = await FindUsersByIds(courseModel.Users);
course.Name = courseModel.Name;
courseRepository.Update(course);
await this.unitOfWork.SaveChangesAsync();
return course;
}
it doesn't work when I want to for example update only Name property.
If Users property doesn't change and there is at least one user it will try to insert record to the CourseUser join table violating primary key constraint instead of 'noticing' that it is already existing in database.
Edit:
Additionally when I use Entry(entityToUpdate).State = EntityState.Unchanged before changing it to modified and move repository.Update() call before overwriting entity properties it works all fine. If somebody could explain this behaviour to me I would be really grateful
In a nutshell, simply don't do this with detaching and setting state to modified. You're already doing the right thing by transporting details to update via a DTO. Let EF's change tracking do it's thing and just call SaveChanges().
I'm not sure what you're concern is warranting the Detach to "prevent accessing". All this does it tell the DbContext to treat the item as untracked, but then by setting the state to Modified, you're immediately tracking it again. The Main reasons you don't want to do this is that by setting the entity state to "Modified" you are telling EF to generate an UPDATE statement for the entire record regardless of whether any value was actually modified rather than optimizing an UPDATE statement to be run for values it detected had changed, and only if they had changed.
The issue you are likely seeing is because of this line:
course.Users = await FindUsersByIds(courseModel.Users);
I would bet money that your FindUsersByIds method is returning a non-tracked set of User entities either by AsNoTracking() or detaching those user entities before returning them. You have already eager loaded users for the given course you loaded. You might think this tells EF to remove existing references and replace them with the desired ones, but it doesn't. It just "adds" a set of Users that EF will treat as new associations where-by some of those associations already still exist in the database.
As a general rule when working with entities. Never, under any circumstances reset/overwrite a collection of related entities. Not to clear it and not to change the values associated with an entity.
If the model contains a revised list of IDs, then the proper way to update the set is to identify any users that need to be added, and any that need to be removed. Load references for the ones that need to be added to associate, and remove any from the eager loaded relationships. Also, where you expect 0 or 1 result, use SingleOrDefault rather than FirstOrdefault. First methods should only be used with an OrderBy clause where you could expect more than one entry and want a reliable, repeatable First. The exception to this would be when working with Linq against in-memory ordered sets where you can guarantee one unique find where First() will perform faster while Single() would scan the entire set. With EF generating queries First() generates a TOP(1) SQL statement where Single() generates a TOP(2) SQL statement so that performance assumption gets shot down to a bare minimum difference to enforce that expectation.
var course = await course.Repository.Query(true)
.Include(c=> c.Users)
.Where(e => e.CourseId == existingCourseId)
.SingleOrDefaultAsync();
if (course == null)
return null;
var existingUserIds = course.Users.Select(u => u.UserId);
var userIdsToAdd = courseModel.Users.Except(existingUserIds).ToList();
var userIdsToRemove = existingUserIds.Except(courseModel.Users).ToList();
if (userIdsToRemove.Any())
{
var usersToRemove = course.Users
.Where(u => userIdsToRemove.Contains(u.UserId))
.ToList();
foreach(var user in usersToRemove)
course.Users.Remove(user);
}
if (userIdsToAdd.Any())
{
var usersToAdd = FindUsersByIds(userIdsToAdd); // Important! Ensure this does NOT detach or use AsNoTracking()
foreach(var user in usersToAdd)
course.Users.Add(user);
}
course.Name = courseModel.Name;
await this.unitOfWork.SaveChangesAsync();
return course;
Basically this inspects the IDs selected to pick out the ones to add and remove, then proceeds to modify the eager loaded collection if needed. The change tracking will take care of determining what, if any SQL needs to be generated.
If the Users collection is exposed as List<User> then you can use AddRange and RemoveRange rather than the foreach loops. This won't work if they are exposed as IList<User> or IHashSet<User> or ICollection<User>.
Edit:
Based on the error mentioned, I would suggest starting by at least temporarily removing some of the variables in the equation around the particular implementation of the Repository and Unit of Work patterns and working with a scoped DbContext to begin with:
For a start try this edit:
using (var context = new AppDbContext())
{
var course = await context.Courses
.Include(c=> c.Users)
.Where(e => e.CourseId == existingCourseId)
.SingleOrDefaultAsync();
if (course == null)
return null;
var existingUserIds = course.Users.Select(u => u.UserId);
var userIdsToAdd =
courseModel.Users.Except(existingUserIds).ToList();
var userIdsToRemove = existingUserIds.Except(courseModel.Users).ToList();
if (userIdsToRemove.Any())
{
var usersToRemove = course.Users
.Where(u => userIdsToRemove.Contains(u.UserId))
.ToList();
foreach(var user in usersToRemove)
course.Users.Remove(user);
}
if (userIdsToAdd.Any())
{
var usersToAdd = FindUsersByIds(userIdsToAdd); // Important! Ensure this does NOT detach or use AsNoTracking()
foreach(var user in usersToAdd)
course.Users.Add(user);
}
course.Name = courseModel.Name;
await context.SaveChangesAsync();
context.Entry(course).State = EntityState.Detached; // Necesssary evil as this method is returning an entity.
return course;
}
This is only intended as a temporary measure to help identify if your repository or unit of work could be leading to transient DbContexts being used to load and track entities. This could still have issues depending on what happens to the Course after this method returns it. In this case since we are leaving the scope of the DbContext instance that is tracking it, we detach it. The next step would be to ensure that a DbContext, either directly or accessible through the unit of work can be injected and that it is guaranteed to be scoped to the web request (if web) or a scope suited to the unit of work this operation is part of. (A bit more work for things like WPF desktop applications)
Normally for something like a web application you would want to ensure that a DbContext has a lifetime scope of the web request (or shorter) but not Transient. We want to ensure that all operations within a unit of work reference the same DbContext instance otherwise you end up working with entities that might be tracked by multiple DbContexts, or start introducing code to mix tracked and untracked (detached) entities to get around problems when passing entity references around. Working with detached entities requires a lot of extra boiler-plate, disciplined coding to ensure DbContexts are working with the correct, single reference of entities to avoid "already tracked" type errors or duplicate data insertion / PK violation exceptions. I.e. pre-checking each and every DbSet.Local in a entity graph of related entities for any currently tracked instances with the same ID and replacing references when wanting to attach an entity graph to a DbContext that isn't already tracking that instance.
Related
This relates to my previous question from a few days ago: EF Core duplicate keys: The instance of entity type '' cannot be tracked because another instance with the key value '' is already being tracked
I am using context.ChangeTracker.TrackGraph to give me more control when inserting data. The insert process works great as show by my own answer on the question above. This problem results when I update items and attempt to save them.
My primary model is as follows (type names have been changed for simplicity):
public class Model
{
public List<Data1> List1 { get; set; }
...
}
Data1 looks like this:
public class Data1
{
public string Name{ get; set; }
...
}
To update to the database, I use this:
using var context = Factory.CreateDbContext();
context.ChangeTracker.TrackGraph(model, node =>
{
if (!(node.Entry.Entity.GetType().IsSubclassOf(typeof(EFChangeIgnore)) && node.Entry.IsKeySet)) //this is a workaround but it solves the pk problems I have been having
{
node.Entry.State = EntityState.Modified;
}
else if (!node.Entry.IsKeySet)
{
node.Entry.State = EntityState.Added;
}
});
return await context.SaveChangesAsync();
This works great as it prevents my existing entities (inheriting fromEFChangeIgnore) from being inserted and otherwise handles updates and inserts of other entities well. However, if I have more than one Data item in List1, I get the error:
The instance of entity type 'Data1' cannot be tracked because another instance with the key value '{ID: 0}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
I understand this error but I am confused as to why it is appearing in this scenario. Yes, both entities have an ID of zero, but only because they have yet to be inserted to the database. What am I doing wrong here?
It turns out my bug was due to a misplaced parenthesis. Go figure.
In my SaveChanges I had accidently negated the entire conditional at the top, such that entities that should have been Modified became Added, and vice versa. This led to multiple entities with an ID of 0 that I intended to add being marked as Modified. EF then complained that two existing entities had the same ID.
That is, this line:
if (!(node.Entry.Entity.GetType().IsSubclassOf(typeof(EFChangeIgnore)) && node.Entry.IsKeySet))
now becomes (conditionals swapped for clarity):
if (node.Entry.IsKeySet && (!node.Entry.Entity.GetType().IsSubclassOf(typeof(EFChangeIgnore))))
And my entire SaveChanges method now is:
using var context = Factory.CreateDbContext();
context.ChangeTracker.TrackGraph(model, node => //all entities inheriting from EFChangeIgnore will not be touched by the change tracker. Other entities with keys will be modified, and items without keys will be added
{
if (node.Entry.IsKeySet && (!node.Entry.Entity.GetType().IsSubclassOf(typeof(EFChangeIgnore)))) //this is a workaround but it solves key duplication problems
{
node.Entry.State = EntityState.Modified;
}
else if (!node.Entry.IsKeySet)
{
node.Entry.State = EntityState.Added;
}
return await context.SaveChangesAsync();
I'm using Sqlite database and System.Data.SQLite 1.0.92
There is 2 table here:
Table Person:
PersonId
PersonName
Table Student:
StudentId
PersonId(reference table Person FK)
StudentNo
Now every time I get the Persons Collection in EF5:
using (var ctx = new myEntities)
{
AllPersons = ctx.Persons.ToList();
}
There is also has AllPersons.student collection will include in the result;
But I don't need it. Of course that's just an example, There is a lot of big table has so many references, it always has performance problems here because of that.
So I'm trying to do not let it in my result. So I change it:
using (var ctx = new myEntities)
{
ctx.Configuration.ProxyCreationEnabled = false;
ctx.Configuration.LazyLoadingEnabled = false;
AllPersons= ctx.Persons.ToList();
}
Now fine, because AllPersons.student collection will always be null
But now I found: If I get Person and Student together:
using (var ctx = new myEntities)
{
ctx.Configuration.ProxyCreationEnabled = false;
ctx.Configuration.LazyLoadingEnabled = false;
AllPersons= ctx.Persons.ToList();
AllStudents = ctx.Student.ToList();
}
Now the reference still include in.
So Is there anyway to don't let the reference include in any time in this situation?
Thank you.
Update
For some friends request, I explain why I need it:
1: When I convert it to json it will be a dead loop. even I already use Json.net ReferenceLoopHandling, the json size very big to crash the server.(if no references, it's just a very small json)
2:Every time I get the client data and need to save, it will display exception about model state, until I set it to null.
Example:
using (myEntities ctx = new myEntities())
{
ctx.Configuration.LazyLoadingEnabled = false;
ctx.Configuration.ProxyCreationEnabled = false;
Person model= ThisIsAModel();
model.students = null; // This is a key, I need set the students collection references to null , otherwise it will throw exception
ctx.Entry(model).State = EntityState.Modified;
ctx.SaveChanges();
}
3: This is More important problem. I already get all data and cache on the server. But It will let the loading time very long when server start. (because the data and references are so many, that is the main problem), I don't know I'll meet what kind of problem again....
public List<Person> PersonsCache; // global cache
public List<Student> StudentsCache; // global cache
using (myEntities ctx = new myEntities())
{
ctx.Configuration.LazyLoadingEnabled = false;
ctx.Configuration.ProxyCreationEnabled = false;
// There is so many references and data, will let it very slow , when I first time get the all cache. even I only get the Person model, not other , just because some Collection has some references problem. It will very slow....
PersonsCache = ctx.Persons.ToList();
StudentsCache= ctx.Student.ToList();
}
The Problem
As you said, when you load both of Parent and Child lists even when LazyLoading is disabled, and then look in parent.Childs you see child items has been loaded too.
var db = new YourDbContext();
db.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet.ToList();
var childList= db.YourChildSet.ToList();
What happened? Why childs are included in a parent?
The childs under a parent entity, are those you loaded using db.YourChildSet.ToList(); Exactly themselves; In fact Entity Framework never loads childs for a parent again but because of relation between parent and child in edmx, they are listed there.
Is that affect Perforemance?
According to the fact that childs only load once, It has no impact on perforemance because of loading data.
But for serialization or something else's sake, How can I get rid of it?
you can use these solutions:
Solution 1:
Use 2 different instance of YourDbContext:
var db1 = new YourDbContext();
db1.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet.ToList();
var db2 = new YourDbContext();
db2.Configuration.LazyLoadingEnabled = false;
var childList= db.YourChildSet.ToList();
Now when you look in parent.Childs there is no Child in it.
Solution 2:
use Projection and shape your output to your will and use them.
var db1 = new YourDbContext();
db1.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet
.Select(x=>new /*Model()*/{
Property1=x.Property1,
Property2=x.Property2, ...
}).ToList();
This way when serialization there is nothing annoying there.
Using a custom Model class is optional and in some cases is recommended.
Additional Resources
As a developer who use Entity Framework reading these resources is strongly recommended:
Performance Considerations for Entity Framework 4, 5, and 6
Connection Management
I'll focus on your third problem because that seems to be your most urgent problem. Then I'll try to give some hints on the other two problems.
There are two Entity Framework features you should be aware of:
When you load data into a context, Entity Framework will try to connect the objects wherever they're associated. This is called relationship fixup. You can't stop EF from doing that. So if you load Persons and Students separately, a Person's Students collection will contain students, even though you didn't Include() them.
By default, a context caches all data it fetches from the database. Moreover, it stores meta data about the objects in its change tracker: copies of their individual properties and all associations. So by loading many objects the internal cache grows, but also the size of the meta data. And the ever-running relationship fixup process gets slower and slower (although it may help to postpone it by turning off automatic change detection). All in all, the context gets bloated and slow like a flabby rhino.
I understand you want to cache data in separate collections for each entity. Two simple modifications will make this much quicker:
Evade the inevitable relationship fixup by loading each collection by a separate context
Stop caching (in the context) and change tracking by getting the data with AsNoTracking.
Doing this, your code will look like this:
public List<Person> PersonsCache;
public List<Student> StudentsCache;
using (myEntities ctx = new myEntities())
{
ctx.Configuration.ProxyCreationEnabled = false;
PersonsCache = ctx.Persons
.AsNoTracking()
.ToList();
}
using (myEntities ctx = new myEntities())
{
ctx.Configuration.ProxyCreationEnabled = false;
StudentsCache= ctx.Student
.AsNoTracking()
.ToList();
}
The reason for turning off ProxyCreationEnabled is that you'll get light objects and that you'll never inadvertently trigger lazy loading afterwards (throwing an exception that the context is no longer available).
Now you'll have cached objects that are not inter-related and that get fetched as fast as it gets with EF. If this isn't fast enough you'll have to resort to other tools, like Dapper.
By the way, your very first code snippet and problem description...
using (var ctx = new myEntities)
{
AllPersons = ctx.Persons.ToList();
}
There is also has AllPersons.student collection will include in the result;
...suggest that Entity Framework spontaneously performs eager loading (of students) without you Include-ing them. I have to assume that your code snippet is not complete. EF never, ever automatically executes eager loading. (Unless, maybe, you have some outlandish and buggy query provider).
As for the first problem, the serialization. You should be able to tackle that in a similar way as shown above. Just load the data you want to serialize in isolation and disable proxy creation. Or, as suggested by others, serialize view models or anonymous types exactly containing what you need there.
As for the second problem, the validation exception. I can only imagine this to happen if you initialize a students collection by default, empty, Student objects. These are bound to be invalid. If this is not the case, I suggest you ask a new question about this specific problem, showing ample detail about the involved classes and mappings. That shouldn't be dealt with in this question.
Explicitly select what you want to return from the Database.
Use Select new. With the select new clause, you can create new objects of an anonymous type as the result of a query and don't let the reference include in. This syntax allows you to construct anonymous data structures. These are created as they are evaluated (lazily). Like this:
using (var ctx = new myEntities())
{
var AllPersons = ctx.People.Select(c => new {c.PersonId, c.PersonName}).ToList();
}
And even you don't need to disable lazy loading anymore.
After running query above:
This query currently allocates an anonymous type using select new { }, which requires you to use var. If you want allocate a known type, add it to your select clause:
private IEnumerable<MyClass> AllPersons;//global variable
using (var ctx = new myEntities())
{
AllPersons = ctx.People
.Select(c => new MyClass { PersonId = c.PersonId, PersonName = c.PersonName }).ToList();
}
And:
public class MyClass
{
public string PersonId { get; set; }
public string PersonName { get; set; }
}
If entities are auto generated, then copy paste it to own code and remove the relation generated like child collection and Foreign key. Or you don't need all this kind of the functionality might be can user lightweight framework like dapper
In normally your student collection doesn't fill from database. it's fill when you reach to property. In addition if you use ToList() method so Entity Framework read data from data to fill your collection.
Pls check this.
https://msdn.microsoft.com/en-us/data/jj574232.aspx#lazy
https://msdn.microsoft.com/en-us/library/vstudio/dd456846(v=vs.100).aspx
Is there anyway to don't let the reference include in any time in this situation?
The solution to this seems to be very simple: don't map the association. Remove the Student collection. Not much more I can say about it.
Decorate any properties with [IgnoreDataMember] if you are using 4.5+
https://msdn.microsoft.com/en-us/library/system.runtime.serialization.ignoredatamemberattribute(v=vs.110).aspx
Also sounds like you are trying to do table inheritance which is a different problem with EF
http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/implementing-inheritance-with-the-entity-framework-in-an-asp-net-mvc-application
http://www.entityframeworktutorial.net/code-first/inheritance-strategy-in-code-first.aspx
If I understand you correctly, you're just trying to make sure you only get what you specifically ask for right?
This was mentioned a little above, but to do this correctly you just want to select an anonymous type.
var students = from s in _context.Students
select new{
StudentId,
StudentNo};
Then, when you want to update this collection/object, I'd recommend use GraphDiff. GraphDiff really helps with the problems of disconnected entities and updates (https://github.com/refactorthis/GraphDiff)
So your method would look similar to this:
void UpdateStudent(Student student){
_context.UpdateGraph(student, map =>
map
.AssociatedEntity(c => c.Person));
_context.SaveChanges();
}
This way, you're able to update whatever properties on an object, disconnected or not, and not worry about the association.
This is assuming that you correctly mapped your entities, and honestly, I find it easier to declare the object as a property, not just the ID, and use a mapping file to map it correctly.
So:
class Person{
int Id{get;set;}
string Name{get;set}
}
class Student{
int Id{get;set;}
string StudentNo{get;set;}
Person Person{get;set;}
public class StudentMap : EntityTypeConfiguration<Student>
{
public StudentMap()
{
// Primary Key
HasKey(t => t.Id);
// Table & Column Mappings
ToTable("Students");
Property(t => t.Id).HasColumnName("StudentId");
// Relationships
HasRequired(t => t.Person)
.HasForeignKey(d => d.PersonId);
}
}
Hopefully that makes sense. You don't need to create a view model, but you definitely can. This way does make it easier to map disconnected items back to the database though.
I had exact same situation.
All I did to solve it was ask for the Student.ToList() before I asked for Persons.ToList()
I didn't have to disable lazy loading. Just need to load the table that has reference to other table first after that you can load the other table and first table results are already in memory and don't get "fixed" with all the references.
They are automatically linked in the ObjectContext by there EntityKey. Depending on what you want to do with your Persons and Students, you can Detach them from the ObjectContext :
using (var ctx = new myEntities)
{
ctx.Configuration.ProxyCreationEnabled = false;
ctx.Configuration.LazyLoadingEnabled = false;
AllPersons= ctx.Persons.ToList();
foreach(var c in AllPersons)
{
ctx.Detach(c);
}
AllStudents = ctx.Student.ToList();
foreach(var c in AllStudents )
{
ctx.Detach(c);
}
}
I have the following 2 classes:
public class Reward
{
public int Id { get; set; }
public int CampaignId { get; set;
public virtual Campaign Campaign { get; set; }
}
public class Campaign
{
public int Id { get; set; }
public virtual ICollection<Reward> Rewards { get; set; }
}
With this I have all the obvious necessary stuff like a DbContext and mappings.
Now let's say I create a Reward entity and insert it like this:
var reward = new Reward { CampaignId = 1 };
context.Set<Reward>().Add(reward);
context.SaveChanges();
reward = context.Set<Reward>().SingleOrDefault(a => a.Id == reward.Id);
//reward.Campaign is null
I obviously have a campaign with Id 1 so the FK constraint is happy. After this insert, my reward entity has it's new Identity Id set. Now the problem is that reward is still just the Reward entity I created. And with this, the reward.Campaign property is null. It seems like EF is keeping the inserted entities in memory, and when I then do a .SingleOrDefault(a => a.Id == reward.Id) it simply returns the entity in memory, and not a new proxy. This is probably a good thing.
So the question is: How does one access or load the navigation properties after an insert or get a new proxy that has the navigation properties as proxies as well.
Am I perhaps inserting in the wrong way?
If I understand you correctly, you're trying to eagerly load a complex property after establishing a relationship via a foreign key property.
SaveChanges() does not do anything in the way of loading complex properties. At most, it is going to set your primary key property if you're adding new objects.
Your line reward = context.Set<Reward>().SingleOrDefault(a => a.Id == reward.Id);
also does nothing in the way of loading Campaign because your reward object is not attached to the context. You need to explicitly tell EF to load that complex object or attach it then let lazy loading work its magic.
So, after you context.SaveChanges(); you have three options for loading reward.Campaign:
Attach() reward to the context so that Campaign can be lazily loaded (loaded when accessed)
context.Rewards.Attach(reward);
Note: You will only be able to lazy load reward.Campaign within the context's scope so if you're not going to access any properties within the context lifespan, use option 2 or 3.
Manually Load() the Campaign property
context.Entry(reward).Reference(c => c.Campaign).Load();
Or if Campaign was a collection, for example Campaigns:
context.Entry(reward).Collection(c => c.Campaigns).Load();
Manually Include() the Campaign property
reward = context.Rewards.Include("Campaigns")
.SingleOrDefault(r => r.Id == reward.Id);
Although, I'd suggest Load since you already have reward in memory.
Check out the Loading Related Objects Section on this msdn doc for more information.
As you are creating your reward object as new Reward(), EF doesn't have a proxy. Instead, create it using DbSet.Create like this:
var reward = context.Set<Reward>().Create();
reward.CampaignId = 5;
context.SaveChanges();
Next attach it to your DbSet:
context.Rewards.Attach(reward);
Finally, you can now use lazy loading to get related entities:
var campaign = reward.Campaign;
I have a simple Solution around the problem.
instead of adding the CampaignID to the reward, add the campaign Object.. so:
var _campaign = context.Campaign.First(c=>c.Id == 1);//how ever you get the '1'
var reward = new Reward { Campaign = _campaign };
context.Set<Reward>().Add(reward);
context.SaveChanges();
//reward.Campaign is not null
Entity framework does all the heavy lifting here.
You're probably thinking that it's a waste to load the entire Campaign object but if you are going to be using it (from what it looks like, seems you are) then I don't see why not.
You can even use the include statement when fetching it above if you need to access navigation properties from the Campaign object...
var _campaign = context.Campaign.include(/*what ever you may require*/).First(c=>c.Id = 1);
In addition to Carrie Kendall and DavidG (in VB.NET):
Dim db As New MyEntities
Dim r As Reward = = db.Set(Of Reward)().Create()
r.CampaignId = 5
db.Reward.Add(r) ' Here was my problem, I was not adding to database and child did not load
db.SaveChanges()
Then, property r.Campaign is available
Did you try using Include()? Something like this:
reward = context.Set<Reward>().Include("Campaigns").SingleOrDefault(a => a.Id == reward.Id);
if you have more than one navigation properties or want to add more than one records it may be hard to do it in these ways.
so i suggest if the memory doesn't matter create a new context object after adding your records , and use it instead
We're using Entity Framework 4.1 for our data acces and while building up objects and we started asking questions to ourselves
about how chatty the application was going to be with the database. Now one item that we really started looking at is below:
public MasterPreAward()
{
public int ID
public int MemberID
public int CycleID
public virtual Cycle
public virtual Member
public virtual Status
public virtual ICollection<DataTracking> DataTrackings
public virtual ICollection<ReviewerAssignment> Reviewers
}
The MasterPreAward is a generated entity from the database and has the navigation properites of Cycle, Member, Status along with two collections for DataTrackings
Reviewers. What we were wondering was, how did Entity Framework load up the child objects based off of these items and bring back the data we use in the follow model?
As you can see, we're passing in MasterPreAward object and then accessing children properties which are loaded based on the MasterPreAward.
public ViewHeaderSummary(MasterPreAward masterPreAward)
{
MasterPreAwardId = masterPreAward.ID;
ClientId = masterPreAward.Cycle.Project.Program.ClientID;
ApplicationId = masterPreAward.MemberID;
ProgramId = masterPreAward.Cycle.Project.ProgramID;
ProjectId = masterPreAward.Cycle.ProjectID;
EventTypeId = masterPreAward.DataTrackings.FirstOrDefault(x=>x.Finished==true
&& x.EventTypeID==(int)FormEvents.Application).EventTypeID;
CycleId = masterPreAward.CycleID;
FormId = masterPreAward.Cycle.CycleForms.FirstOrDefault().FormID;
}
What we'd like to know, is this the best way to access these properties, or should be really be thinking doing this type of work in a different way?
I believe the default settings would be to lazy load each nested collection independently, which could cause a lot of database traffic.
The best way to verify the generated SQL is to start a SQL profiler and confirm the number of queries.
You can force EF to eagerly load related entities by calling .Include method. See here for more details.
You don't seem to query for full entities but only for a bunch of scalar values. In my opinion this would be a good candidate for a projection which collects all the needed values in a single database roundtrip:
var result = dbContext.MasterPreAwards
.Where(m => m.ID == masterPreAward.ID)
.Select(m => new
{
ClientId = m.Cycle.Project.Program.ClientID,
ProgramId = m.Cycle.Project.ProgramID,
ProjectId = m.Cycle.ProjectID,
EventTypeId = m.DataTrackings.Where(d => d.Finished
&& x.EventTypeID==(int)FormEvents.Application)
.Select(d => d.EventTypeID).FirstOrDefault(),
FormId = m.Cycle.CycleForms.Select(c => c.FormID).FirstOrDefault()
})
.Single();
MasterPreAwardId = masterPreAward.ID;
ClientId = result.ClientID;
ApplicationId = masterPreAward.MemberID;
ProgramId = result.ProgramID;
ProjectId = result.ProjectID;
EventTypeId = result.EventTypeId;
CycleId = masterPreAward.CycleID;
FormId = result.FormID;
As you can see, you need the DbContext to run such a query.
Your original way to lazily load all related entities will lead to 5 database queries as far as I can see (for Cycle, Project, Program, DataTrackings and CycleForms). Worst of all are the queries for DataTrackings.FirstOrDefault and CycleForms.FirstOrDefault which will actually load the full collections first from the database into memory and then execute FirstOrDefault in memory on the loaded collections to return only one single element from which you then only use one single property.
(Edit: Query for ApplicationId and CycleId not necessary, Code changed.)
Let's say I have a User Entity, and created partial User class so I can add some methods (like with NHibernate). I added GetByID to make getting user easier:
public static User GetByID(int userID)
{
using (var context = new MyEntities())
{
return context.Users.Where(qq => qq.UserID == userID).Single();
}
}
Now, somewhere in business logic I'd like to do something like this:
var user = User.GetByID(userID);
var posts = user.GetAllPostsForThisMonth();
foreach(var post in posts)
{
Console.WriteLine(post.Answers.Count);
}
GetAllPostsForThisMonth() is similar to GetByID - has context and is disposing it right after execution.
Normally I can't do this because context is disposed when I call post.Answers.Count. This, I think, renders my methods useless... Or am I missing something? Can I anyhow use my entities like this? Or should I create method for every single query I use (like post.GetAnswersCount())? Thanks in advance!
The behavior you're lamenting is actually good, because it keeps you from shooting yourself in the foot. If you had been allowed to do this, it would have cause n round-trips to the database (where n is the number of posts), and each one of those round-trips would have pulled all the data for all the Answers, when all you wanted was the Count. This could have an enormous performance impact.
What you want to do is construct an object that represents all the information you expect to need from the database, and then construct a LINQ query that will actually load in all the information you expect to use.
public class PostSummary
{
public Post Post {get;set;}
public int AnswerCount {get;set;}
}
public IEnumerable<PostSummary> GetPostSummariesByUserAndDateRange(
int userId, DateTime start, DateTime end)
{
using (var context = new MyEntities())
{
return context.Posts
.Where(p => p.UserId == userId)
.Where(p => p.TimeStamp < start && p.TimeStamp > end)
.Select(new PostSummary{Post = p, AnswerCount = p.Answers.Count()})
.ToList();
}
}
This produces a single SQL query and, in a single round-trip, produces exactly the information you wanted without loading in a ton of information you didn't want.
Update
If NHibernate works anything like Java's Hibernate, it won't do lazy loading after the context is disposed, either. Entity Framework does give you a lot of options along these lines: which one works best for you will depend on your particular situation. For example:
Rather than keeping your context scoped inside the data-access method, you can make the context last longer (like once per request in a web application), so that Lazy loading of properties will continue to work beyond your data-access methods.
You can eagerly load whatever entity associations you know you're going to need.
Here's an example of eager loading:
public GetAllPostsAndAnswersForThisMonth()
{
using (var context = new MyEntities())
{
return context.Posts.Include("Answers")
.Where(p => p.UserID == UserID)
.ToList();
}
}
However, since Entity Framework basically constitutes your "Data Access" tier, I would still argue that the best practice will be to create a class or set of classes that accurately model what your business layer actually wants out of the data tier, and then have the data access method produce objects of those types.
One method is to explicitly load related objects that you know you will need before you dispose the context. This will make the related data available, with the downside that if you don't need the related info it is wasted time and memory to retrieve. Of course, you can also handle this with flags:
... GetAllPostsForThisMonth(bool includeAnswers)
{
using (var context = new MyEntities())
{
context.ContextOptions.LazyLoadingEnabled = false;
// code to get all posts for this month here
var posts = ...;
foreach (var post in posts)
if (!post.Answers.IsLoaded)
post.Answers.Load();
return posts;
}
}