Entity Framework Lazy Loading Unexpected Property Population on Extracted Data - c#

See following simplified example:
Student Class:
public class Student
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public Grade Grade { get; set; }
}
Grade Class:
public class Grade
{
public int GradeId { get; set; }
public string GradeName { get; set; }
public ICollection<Student> Students { get; set; }
}
Context class:
public class SchoolContext : DbContext
{
public SchoolContext() : base()
{
Configuration.LazyLoadingEnabled = true;
}
public DbSet<Student> Students { get; set; }
public DbSet<Grade> Grades { get; set; }
}
Program:
static void Main(string[] args)
{
using (var ctx = new SchoolContext())
{
Grade[] grades = ctx.Grades.ToArray();
Console.WriteLine(grades[0].Students == null); // True - As expected
var students = ctx.Students.ToArray();
Console.WriteLine(grades[0].Students == null); // False - ? Did not expect that
}
Console.Read();
}
The following happens:
Lazy loading was enabled
List of Grades was saved into an array
As expected, Students navigation property of grade objects was null
Made a separate query to get Students
EF somehow filled up the Students navigation property of the array in memory.
This could end up in very expensive payloads to clients if not used with care.
Can anyone explain why and how did the navigation properties got populated in the array?

The reason the grades[0].Students is loaded after doing a query to get students from your database with ctx.Students.ToArray(); is that you DbContext is tracking changes.
This is explained in Entity Framework docs:
Tracking behavior controls whether or not Entity Framework Core will keep information about an entity instance in its change tracker. If an entity is tracked, any changes detected in the entity will be persisted to the database during SaveChanges(). Entity Framework Core will also fix-up navigation properties between entities that are obtained from a tracking query and entities that were previously loaded into the DbContext instance.
This is the EF Core docs, but this also applies to EF6 for .NET Framework.
If you want to disable this behavior, you may load your entities as no tracking:
ctx.Grades.AsNoTracking().ToArray();
...you could also disable it by default (e.g inside the DbContext constructor), the same way you do for lazy load.
Another way you could do that is to manually detach an object from the context.
Then if you ever intended to make any changes and persist it to database, you should reattach your entity after querying students, and before making your changes:
using (var ctx = new SchoolContext())
{
Grade[] grades = ctx.Grades.ToArray();
Grade firstGrade = grades[0];
Console.WriteLine(firstGrade.Students == null); // True - as expected
ctx.Grades.Detach(firstGrade); // stop tracking changes for this entity
var students = ctx.Students.ToArray();
Console.WriteLine(firstGrade.Students == null); // True - still null
// Let's reattach so we can track changes and save to database
ctx.Grades.Attach(firstGrade);
firstGrade.GradeName = "Some new value"; // will be persisted, as this is being tracked again
ctx.SaveChanges();
}
Also, it's worth mentioning with lazy load enabled, accessing grades[0].Students the first time should make EF load that navigation property if it was not loaded yet (which is precisely its purpose), however it seems this is not happening because your navigation property is not virtual.

Related

Is there a way to fill navigation properties without saving an entity to the database?

Let's say I have an entity PersonEntity with a property EyeColorId and navigation property of type EyeColorEntity. Is there a way to prefetch the EyeColorEntity without saving the entity and without just querying for the EyeColor.
Ex.
public class PersonEntity
{
public int Id { get; set; }
public int EyeColorId { get; set; }
public EyeColorEntity EyeColor { get; set; }
}
public void FillFromDbExample(DbContext context)
{
var personEntity = new PersonEntity()
{
EyeColorId = 5
};
context.SetNavigationProperties(personEntity);
}
Theoretically this would fill the navigation property on the entity. The result would look something like this.
personEntity =
{
EyeColorId = 5,
EyeColor =
{
Id = 5,
Color = "Blue"
}
}
The trick here is that I don't want to have to individually query for each property and I don't want to have to save the entity to the database to pull these properties back. Does something like the described functionality exist in EnityFramework 6.2.0.
You can pre-load all properties in the context to make relationship fixup work. For example:
context.EyeColors.Load();
var personEntity = new PersonEntity()
{
EyeColorId = 5
};
context.Persons.Attach(personEntity);
In the last statement EF automatically populated personEntity.EyeColor.
Alternatively, you can rely on lazy loading by initializing the entity as a lazy-loading proxy. The property must be virtual to enable proxy creation:
public virtual EyeColorEntity EyeColor { get; set; }
Then:
var personEntity = context.Persons.Create(); // Creates a proxy
personEntity.EyeColorId = 5;
context.Persons.Attach(personEntity);
Now EF will query the matching EyeColor from the database when personEntity.EyeColor is accessed (= lazy loading).
I don't want to have to individually query for each property
Lazy loading does query properties individually, but it's not you who has to do it.
Note that in both cases personEntity must be attached to the context.

EntityFramework 6 DatabaseFirst disable lazy loading

How disable all lazy loading
After generation model from database first I have
public partial class company
{
public int id { get; set; }
public string name { get; set; }
public virtual ICollection<user> user { get; set; }
}
public partial class user
{
public int id { get; set; }
public int company_id { get; set; }
public virtual company company { get; set; }
}
I want load only user and their company
db = new Entities();
db.Configuration.ProxyCreationEnabled = false;
db.Configuration.LazyLoadingEnabled = false;
var result = db.user.Include(x => x.company).ToList();
I don`t want load result[0].company.user
Now collection filled with all users of company
result[0].company.user must be null
if i want load result[0].company.user I want use .Include(x => x.company.user)
This is not lazy-loading but a different concept called relationship fix-up. In short it just keeps navigation properties in sync with each other. In your case you have user.company navigation property and company.user collection navigation property. You load both user and company and they are attached to the context. Now at certain points (list of such points you can find here) EF performs relationship fix-up. In this case it happens after query is performed against DbSet. EF ensures that if user.company is set - company.user collection should contain that user also, because those are related navigation properties. This user is already attached to the context (you originally loaded it with your query) so no additional queries are made to database, so it's not lazy loading.
With lazy loading, if you have 100 users for company A then companyA.user will contain 100 entries (loaded from database when you access this property). In your case, even if company A has 100 users - companyA.user will contain just 1 user - that one which you originally loaded.
This behaviour is usually fine, though in some cases it might cause troubles - most often this happens when you want to serialize your EF object and step into circular references because of that.
There is no way to disable this behavior that I'm aware of.

Differential save on child entities with Entity Framework 7.0.0-rc1 / EntityFrameworkCore 1.0.0

I have an application based on ASP.Net MVC 6 / ASP.Net Core 1.0, which exposes CRUD methods through a Web API.
I am using Entity Framework 7.0.0-rc1, I plan to migrate everything to EF Core 1.0 / ASP.Net Core 1.0 but haven't done this yet.
Here's a snippet of the type of model which causes me an design issue :
public class ParentEntity
{
public int Id { get; set; }
public virtual ICollection<ChildEntity> ChildCollection { get; set; } = new ChildCollection();
}
public class Child
{
public int Id { get; set; }
public virtual ParentEntity ParentEntity { get; set; }
public int? ParentEntityId { get; set; }
}
All relationships are properly ensured in my ApplicationContext class, and all CRUD operations work as expected, including Adds and Updates of the Child entities when a ParentEntity is saved.
For example, here's the kind of Save method I expose through the API :
// On some web API Save method :
[HttpPost]
public JsonResult Save(ParentEntity entity)
{
if (entity.Id > 0) _context.Update(entity);
else _context.Add(entity);
var result = _context.SaveChanges();
return Json(result);
}
The entity passed to the save method is coming from my AngularJS backend. As long as child entities have been added or modified, everything works as expected.
Let's now imagine the back end has received a ParentEntity loaded with two ChildEntity named A and B.
Now on the backend I remove the B child entity and post it to the save method.
The backend saves the changes of the data it receives : all updates are persisted on ParentEntity and on ChildEntity A.
But the B ChildEntity remains in the datastore, which seem logical since there was no indication on what to do with it.
So my question is, since it seems to me it is a very common case : how do I deal with that ?
I have imagined storing somewhere the child collection, save the changes, reload the root entity, compare the collection, determine which should be removed, then remove it, but it seems pretty hard and dirty work to me. Is there a better way ?
The answer to your question is here
https://msdn.microsoft.com/da-dk/magazine/mt694083
According to some tips explained in the MSDN blog post above, I have implemented a Client state field on every entity, through it's base class :
public enum ObjectState
{
Unchanged = 0,
Added = 1,
Modified = 2,
Deleted = 3
}
public interface IObjectWithState
{
ObjectState ClientState { get; set; }
}
public abstract class BaseEntity : IBaseEntity, IPrimaryKey
{
public int Id { get; set; }
public string Name { get; set; }
[NotMapped]
public bool IsNew => Id <= 0;
[NotMapped]
public ObjectState ClientState { get; set; } = ObjectState.Unchanged;
}
Whenever a child entity is removed from a collection on the client side, instead of being physically removed and never posted back, I will keep it on the collection, and mark it as deleted.
Then I just have to deal with that on server side :
_context.ChildEntityCollection.RemoveRange(
entity.ChildCollection.Where(c => c.ClientState == ObjectState.Deleted).ToList()
);
_context.SaveChanges();
I implemented the whole enum on every entity, but currently I only use the Deleted value, and only through a relation. Up till now, no need for other values, or any value on root entities.

EF6 lazy loading doesn't work while eager loading works fine

I updated my app from EF4.1 to EF6 and now I've got lazy loading issue. I used EF6.x DbContext Generator to generate new DbContext. All of the advices from this article are also applied.
My classes are public
Not sealed, not abstract
Have a public constructor
Don't implement neither IEntityWithChangeTracker nor IEntityWithRelationships
Both ProxyCreationEnabled and LazyLoadingEnabled are set to true
Navigation properties are virtual
What also looks wierd to me is that if I explicitly include navigation property with Include("...") it gets loaded.
Simplified version of my POCOs and DbContext:
public partial class Ideation
{
public Ideation()
{
}
public long Id { get; set; }
public Nullable<long> ChallengeId { get; set; }
public virtual Challenge Challenge { get; set; }
}
public partial class Challenge
{
public Challenge()
{
this.Ideations = new HashSet<Ideation>();
}
public long Id { get; set; }
public virtual ICollection<Ideation> Ideations { get; set; }
}
public partial class BoxEntities : DbContext
{
public TIBoxEntities()
: base("name=BoxEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Ideation> Ideations { get; set; }
public virtual DbSet<Challenge> Challenges { get; set; }
}
Also I tried to set ProxyCreationEnabled and LazyLoadingEnabled explicitly without no luck. The entity isn't loaded as a dynamic proxy as this debug session screenshot shows:
What else am I missing?
A situation where this could happen is that the entity you are trying to load with Find is already attached to the context as a non-proxy object. For example:
using (var context = new MyContext())
{
var ideation = new Ideation { Id = 1 }; // this is NOT a proxy
context.Ideations.Attach(ideation);
// other stuff maybe ...
var anotherIdeation = context.Ideations.Find(1);
}
anotherIdeation will be the non-proxy that is already attached and it is not capable of lazy loading. It even wouldn't help to run a DB query with var anotherIdeation = context.Ideations.SingleOrDefault(i => i.Id == 1); because the default merge option for queries is AppendOnly, i.e. the new entity would only be added if there isn't already an attached entity with that key. So, anotherIdeation would still be a non-proxy.
You can check if the entity is already attached by using Local before you call Find in your GetById method:
bool isIdeationAttached = context.Ideations.Local.Any(i => i.Id == id);
Per #ken2k's comment, the default for new models starting with EF 4 was to enable lazy loading by default. With EF 1, it was not allowed. If you migrated your model from 1 to 4, it is kept off by default. You would need to modify the context to turn that on.
That being said, you indicate through debugging that it is true. In that case, check your usage scenarios. Is it possible your context has been disposed prior to fetching the child objects. Typically we see this when data binding to the LINQ query where the context is configured in a Using block and the actual iteration doesn't happen until after the using block's scope has passed.

DbContext - Best Practice for Saving Child Collections when Working with Disconnected Entities

I'm attempting to separate my DbContext from a winforms application that I'm currently using to better support a multi-user environment as well as an upcoming website. After doing a bit of research I've going with implementing a data access layer (DAL) for the winforms app/website to connect to and having the end-users work with disconnected entities. My question is regarding the best way I would go about saving updates to my entities when one of the entities in a child collection has been updated.
For instance, if I have the following structure (simplified)
public class Company
{
public int CompanyID { get; set; }
public string CompanyName { get; set; }
public ICollection<Employee> Employees { get; set; } // Non-virtual as we aren't lazy-loading
}
public class Employee
{
public int CompanyID { get; set; }
public int EmployeeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Claim> Claims { get; set; }
}
public class Claim
{
public DateTime ClaimDate { get; set; }
public ICollection Documentation { get; set; }
}
public class Document
{
public byte[] DocumentImage { get; set; }
public string Name { get; set; }
public DateTime CreateDate { get; set; }
}
Inside the winforms application, I have multiple Binding Source's set-up to display the employee's information
For Example:
employeeBinding.DataSource = typeof(Employee); // Eventually set to an IEnumerable<Employee>
claimBinding.DataSource = employeeBinding;
claimBinding.DataMember = "Claims";
documentationBinding.DataSource = claimBinding;
documentationBinding.DataMember = "Documentation";
However, by setting things up like this I'm unable to make calls on the "CurrentChanged" event of each binding source to save each entity since it has changed (unless I have references stored to the previous entity inside the form). So what I have thought to do was something similar to below in the DAL and iterate through each of the child collections.
public void UpdateEmployee(Employee employee)
{
using (myContext context = new myContext())
{
context.Employees.Attach(employee);
context.Entry<Employee>(employee).State = EntityState.Modified;
foreach(var claim in employee.Claims)
{
context.Entry<Claim>(claim).State = EntityState.Modified;
foreach(var doc in claim.Documentation)
{
context.Entry<Document>(doc).State = EntityState.Modified;
}
}
context.SaveChanges();
}
}
However, I feel that this route can get ugly quick with some more complex entities and relationships. Could someone help point me to the best route to handle this or should I have references to the current entities in the code so when the "CurrentChanged" event fires I can just update each individual entity?
Thank you very much.
When you work with Entity Framework you have the ChangeTracker, even if you are using this "Disconected entities" you can have the ChangeTracker tracking the entities, to have this you just need to attach them to the context and before you call the SaveChanges you call .DetectCHanges() You dont really need to have this specific code, you can use generics for this:
public void Update<TEntity>(TEntity entity)
{
using (myContext context = new myContext())
{
context.Set<TEntity>.Attach(entity);
context.ChangeTracker.DetectChanges();
context.SaveChanges();
}
}
the call to the method would be:
Update<Employee>(employees);
Also i think is better for you to use a BindingSouce as the DataSource, and set the DataSource of the BindingSource as a List instead of typeof(Employee)
I could be wrong but I don't believe DetectChanges will be able to determine that there have been changes made to a disconnected entity. When the entity is attached, it will have an EntityState of "Unchanged" so wouldn't the DbContext do nothing with it until you mark it's state as "Modified". Also, as indicated in the following URL, "DetectChanges" is called for a number of methods (including "Attach") anyways and the explicit call would not be needed.
http://msdn.microsoft.com/en-us/data/jj556205.aspx
As for the BindingSource, I was illustrating that that BindingSource will be set to typeof(Employee) as if I was setting up my code in the constructor before the load events where I would actually get my data and set it's datasource to an IEnumerable from the DAL call. If I didn't do this, I would run into issues when attempting to bind to the "DataMember" properties as the other BindingSources wouldn't be able to find the properties indicated.
I don't believe that the code you provided as a sample fixes the issue I'm running into regarding child collections being updated. When testing with LinqPad they'll be updated if the parent entity has changed as well, but not if there have been zero changes to the parent. That's why I was iterating through all child collections and marking them as "Modified".

Categories

Resources