eager loading with dbcontext and projection - c#

We are converting our project from ObjectContext to dbContext.
Our current problem is with the difference in how eager loading is handled.
Example context
public class Person
{
public virtual ICollection<Email> Emails { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Email
{
public string Address{ get; set; }
}
public class Post
{
public string Content{ get; set; }
}
we have many pieces of code throughout the enterprise that expect the emails to be loaded and therefore call person.Emails.First() without thinking about it.
So we need to make sure that Emails are eagerly loaded.
Sometimes we can just us Include
However, when we use projections in our data layer we are running into problems. i.e.
return context.Persons.Select(p=> new Top5VM {
Person = p,
TopPosts = p.Posts.Take(5)
};
We have a lot of code that relies on Top5VM and expects Person.Emails to be loaded.
No mater what we've tried we can not figure out where to put the Include (or Load) function call where it will actually make a difference .
With the ObjectContext we would just have a dummy property on the Top5VM called Emails. Once that was loaded, the ObjectContext had references to all of those Entities an therefore never needed to go back to the server even when we accessed them through the person object. But that no longer works with the DbContext

Figured it out. I need to set context.Configuration.LazyLoadingEnabled = false; any time I want to use projection to accomplish eager loading. Unless of course I want to access the loaded entities directly from the projection.

You can do this by always using your own DbContext in between:
public class MyDbContext : DbContext
{
public MyDbContext() : base()
{
Configuration.LazyLoadingEnabled = false;
}
}
Then use this everywhere instead of a DbContext.
Alternately, if you have control of all the POCOs, you could just remove the virtual keyword from the related collections ... the virtual keyword is the magic hookup Microsoft uses for lazy loading.
EDIT
I also tend to do this as an extension method:
static class DbContextExtensions
{
public static DbContext AsEagerLoadingContext(this IDbContext context)
{
context.Configuration.LazyLoadingEnabled = false;
//context.Configuration.AutoDetectChangesEnabled = false;
return context;
}
}
Used as so, allowing lazy loading to be used or not as needed:
using (var context = new DbContext().AsEagerLoadingContext())
context.Stuff.Select(s => s.AllTheThings);

Related

EF Core is lazy loading when I try to eager load with .Include()

I'm using EF Core 2.2.6 (database first) and it seems like just having lazy loading enabled keeps me from being able to eager load. Does enabling lazy loading preclude using eager loading in any capacity?
namespace Example.Models
{
public class Lead
{
public int Id { get; set; }
public LeadOrganization LeadOrganization { get; set; }
public Lead(ExampleContext.Data.Lead dbLead)
{
Id = dbLead.Id;
LeadOrganization = new LeadOrganization(dbLead.LeadOrganization);
}
public static Lead GetLead(int id)
{
using (var db = new ExampleContext())
{
var dbLead = db.Leads
.Include(l => l.LeadOrganization)
.ThenInclude(lo => lo.LeadOrganizationAddresses)
.ThenInclude(loa => loa.AddressType)
.FirstOrDefault(l => l.Id== id);
return new Lead(dbLead);
}
}
}
}
namespace Example.Models
{
public class LeadOrganization
{
public IEnumerable<LeadOrganizationAddress> Addresses { get; set; }
public LeadOrganization(ExampleContext.Data.LeadOrganization dbLeadOrganization)
{
Addresses = dbLeadOrganization.LeadOrganizationAddresses.Select(loa => new LeadOrganizationAddress(loa));
}
}
}
namespace Example.Models
{
public class LeadOrganizationAddress
{
public AddressType AddressType { get; set; }
public LeadOrganizationAddress(ExampleContext.Data.LeadOrganizationAddress dbLeadOrganizationAddress)
{
AddressType = new AddressType(dbLeadOrganizationAddress.AddressType);
}
}
}
namespace Example.Models
{
public class AddressType
{
public short Id { get; set; }
public AddressType(ExampleContext.Data.AddressType dbAddressType)
{
Id = dbAddressType.Id;
}
}
}
The ExampleContext.Data namespace contains the EF-generated partial classes from the database. Lead, LeadOrganization, LeadOrganizationAddress, and AddressType are classes that are basically 1:1 with the partials in terms of properties, but with static methods added (yes it's weird, but it's what I have to work with).
A Lead has a LeadOrganization, which in turn has at least one LeadOrganizationAddress, which in turn has an AddressType.
When GetLead calls the Lead constructor, the data from the query has not loaded, even though it should be eager loaded. This leads to problems down the line of nested objects. When it eventually gets to the LeadOrganizationAddress constructor, the DbContext has been disposed, and thus can't lazy load the associated AddressType.
Am I misunderstanding the whole point of eager loading? I thought it would retrieve all the data upon the initial query, letting me then pass that to the constructor without issue. I shouldn't need to keep going back to the database and lazy load anything.
Can you simply not eager load if you have lazy loading enabled? Is there some other workaround like forcing it to load any proxied entities?
Ok, after investigating the issue, there is a problem with EF Core 2.x lazy loading via proxies implementation. The related tracked issues are
#15170: Eager loading include with using UseLazyLoadingProxies
#12780: Store IsLoaded flags in proxy for better lazy-loading experience
The problem is that the navigation properties are eager loaded, but LazyLoader does not know that when disposed - can't safely access context change tracker and simply is throwing exception. The relevant code can be seen here, in the very first line:
if (_disposed)
{
Logger.LazyLoadOnDisposedContextWarning(Context, entity, navigationName);
}
As I read it, it's supposed to be fixed in EF Core 3.0 when released with the following "breaking change" - Lazy-loading proxies no longer assume navigation properties are fully loaded. It also partially explains the current problem:
Old behavior
Before EF Core 3.0, once a DbContext was disposed there was no way of knowing if a given navigation property on an entity obtained from that context was fully loaded or not.
Unfortunately this doesn't help you with the current problem. The options I see are:
Wait for EF Core 3.0 release
Don't use lazy loading via proxies
Turn off the lazy loading on disposed context warning - by default it is Throw, change it to Log or Ignore, for instance:
optionsBuilder.ConfigureWarnings(warnings => warnings
.Log(CoreEventId.LazyLoadOnDisposedContextWarning)
);
I assume you use UseLazyLoadingProxies() but want to disable the lazy loading for specific Includes in your queries. This is not implemented yet:
https://github.com/aspnet/EntityFrameworkCore/issues/10787
The only thing what you can do right now:
1.) Disable the lazy-loading proxy ("default lazy loading for all properties")
2.) Then use (manual implemented) lazy-loading for specific properties, for example in one of your cases:
public class LeadOrganization
{
private ILazyLoader _lazyLoader { get; set; }
private IEnumerable<LeadOrganizationAddress> _addresses;
public LeadOrganization(ILazyLoader lazyLoader)
{
_lazyLoader = lazyLoader;
}
public IEnumerable<LeadOrganizationAddress> Addresses
{
get => _addresses;
set => _addresses = value;
}
public IEnumerable<LeadOrganizationAddress> AddressesLazy
{
get
{
_lazyLoader?.Load(this, ref _addresses);
}
set => this._addresses = value;
}
}
So for eager-loading use .Include(lo=>lo.Addresses), for lazy-loading use .Include(lo=>lo.AddressesLazy)
Edit 1
IMO lazy loading shouldn't be enabled per default for all properties - this could impact the performance of your whole implementation. So the solution above is one alternative in cases where lazy loading brings you advantages. I also would like to have this option in every include, something like .Include(o=>o.Addresses, LoadingBehaviour.Eager) - maybe this will exist in the future.
Lazy loading isn't what's preventing the instantiation of your properties, lacking a proper constructor in them is.
EF Core aside, it's very strange that those types, e.g. LeadOrganization need to be passed in an instance of themselves in their constructor. It's kind of a chicken and egg problem – how do you create the first one?
public class LeadOrganization
{
public IEnumerable<LeadOrganizationAddress> Addresses { get; set; }
public LeadOrganization(ExampleContext.Data.LeadOrganization dbLeadOrganization)
{
Addresses = dbLeadOrganization.LeadOrganizationAddresses.Select(loa => new LeadOrganizationAddress(loa));
}
}
In any case, EF Core only supports simple constructors with parameters based on convention (basically a 1-1 mapping of parameters to properties), so it doesn't know how to instantiate and hydrate those nested classes, eager or lazy.
I suggest making those constructors parameterless, or if you want EF to hydrate the objects via properties, at least add a parameterless constructor to your classes.

How to stop Entity Framework from including properties I don't want it to?

When reading an entity type from my DataContext, I get all the associated objects when I don't want them. How do I set EF up so I only do explicit loading?
Reading up on msdn info like from here:
https://devblogs.microsoft.com/dotnet/announcing-entity-framework-core-2-2/
It seems like I should get nothing for free, so explicit loading is the way, however I'm using the code below but my results are more than I would expect.
public class TalesContext : DbContext
{
public TalesContext()
{
}
public TalesContext(DbContextOptions<TalesContext> options) : base(options)
{
}
protected internal DbSet<Story> Stories { get; set; }
protected internal DbSet<Event> Events { get; set; }
protected internal DbSet<StoryEventMention> EventMentions { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.IsConfigured) return;
optionsBuilder.UseInMemoryDatabase("TalesTesting");
}
}
public class Event
{
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public IList<StoryEventMention> EventMentions { get; set; }
[MaxLength(128)]
public string Title { get; set; }
}
var query = from e in TalesContext.Events select e;
// various query.Where
query = query.Skip((pageNumber - 1) * pageSize).Take(pageSize);
return query.ToList();
I would expect to get a list of Events with the Ids and Titles populated and EventMentions as null. However I get EventMentions populated along with all further navigation properties. Pretty much the entire test database.
I get this when I run a unit test and when I expose this through an API.
I found my mistake.
My EventFetcher class is a singleton, registered through IoC. It has a reference to TalesContext also registered as a singleton through IoC. So I had one DataContext through the application. So when the first request came in, it seeded the database - and so had everything in it. Thus all the references between the objects were already built and when I requested one without explicitly including it, the context returned the data it already had with everything attached.
I did a restructure on this so that a new context is injected into the controller each request and the behaviour is exactly what you would expect. So the lesson here is to be mindful of the age and persistence of your data context if you start getting unexpected results when querying.
Thanks for the assistance!
To answer your question. I believe what you need to do is
inside your TalesContext call this :
this.Configuration.LazyLoadingEnabled = false;
Sources:
explains lazy loading:
https://www.entityframeworktutorial.net/lazyloading-in-entity-framework.aspx
Explains the difference between eager loading and lazy loading
https://learn.microsoft.com/en-us/aspnet/mvc/overview/getting-started/getting-started-with-ef-using-mvc/reading-related-data-with-the-entity-framework-in-an-asp-net-mvc-application

WCF and EntityFramework - using virtual ICollection

I have a WCF-Service, which should return a User with a list of Tasks.
Here is the code of my operation:
//in the Interface IService
[OperationContract]
User getUser();
//in the Service
public User getNewUser()
{
return new User();
}
And here my classes (just like that to make it short, they are in different files):
[Table("user", Schema = "public")]
public class User: ModelBase
{
public string Name{ get; set; }
public virtual ICollection<Task> TaskList { get; set; }
}
[Table("task", Schema = "public")]
public class Task: ModelBase
{
public DateTime Date { get; set; }
public string Description { get; set; }
}
My Problem: using the virtual ICollection gets me an error when i call the WCF-Method (in that case getNewUser, it does not matter if there is something in it). Removing the "virtual" if the ICollection makes the WCF-Operation working, but then the Users i get from the database don't have the Tasks in their list. How is the correct way to fix this?
TL; DR answer : The problem comes from EF only. You should deactivate the Lazy Loading (through the configuration) and use Eager Loading instead : https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
From my conclusions, when you use Lazy Loading, the navigation properties are never null. EF leaves marks (leave a breakpoint and explore your values to see it) so EF can load data on demand (outside the initial query) and WCF can't serialize these "marks" when it wants to send a response.
The easiest way of preventing that is to use eager loading. You could also set to null all unnecessary navigation properties before sending the response to the client, but it's not really recommended...

EF Code First Lazy loading Not Working

I am using code first with EF6 but cannot seem to get lazy loading to work. Eager loading is working fine. I have the following classes:
public class Merchant : User
{
...
public virtual ICollection<MerchantLocation> MerchantLocations { get; set; }
}
public class MerchantLocation : BaseEntity
{
...
public int MerchantId { get; set; }
public virtual Merchant Merchant { get; set; }
}
public class User : BaseEntity
{
...
}
public class BaseEntity
{
...
public int Id { get; set; }
}
I test my lazy loading of the locations via the following code (which fails):
public void Test_Lazy_Loading() {
using (var context = new MyDbContext()) {
var merchant = context.Users.OfType<Merchant>.First();
merchant.MerchantLocations.ShouldNotBeNull(); // fails
}
}
However eager loading works fine:
public void Test_Eager_Loading() {
using (var context = new MyDbContext()) {
var merchant = context.Users.OfType<Merchant>.Include("MerchantLocations").First();
merchant.MerchantLocations.ShouldNotBeNull(); // passes
}
}
MerchantLocations is marked as public virtual so I'm not sure what the problem is. I have also added the following in my DbContext constructor:
Configuration.LazyLoadingEnabled = true;
Configuration.ProxyCreationEnabled = true;
edit: I have also noticed that the merchant object being returned in the above tests is not an EF proxy. It is a plain Merchant. I suspect that this is causing the problem.
I realized that the problem was that the Merchant class did not meet requirements for proxy generation. Specifically, I needed to add a protected parameterless constructor. I only had a private one.
Another thing that can cause lazy loading to fail is navigation properties that are not virtual. That was not the case for OP, but this question is a top Google result so it may help some.
And yet another possible cause is a mapped database column that doesn't exist. I was surprised to see that break lazy loading rather than throw a database exception.

Why are my navigational properties null when retrieved from the database in EF 4.2 POCO?

I have a exceedingly simplistic data model (below). I am having trouble figuring out how I am to get my navigational properties to load from the database. I have no trouble getting them in, but the navigational property does not get set by EF it appears. I have seen several related questions, but they are slightly different or rather involved. I am looking for information on how navigational properties are treated by EF 4.2 (POCO). In the reading I've done, I got the impression that I would be able to access objects with foreign keys using navigational properties. Instead, my properties are coming back as either null or empty depending on if I instantiate my collection in the constructor.
public class AnimalDb : DbContext
{
public static AnimalDb Create(string fileName)
{
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
return new AnimalDb(fileName);
}
private AnimalDb(string fileName) : base(fileName) { }
public DbSet<Animal> Animals { get; set; }
}
public class Animal
{
public Animal()
{
Id = Guid.NewGuid();
Traits = new ObservableCollection<Trait>();
}
public Guid Id { get; set; }
public string Species { get; set; }
public string Name { get; set; }
public ObservableCollection<Trait> Traits { get; set; }
}
public class Trait
{
public Trait()
{
Id = Guid.NewGuid();
}
public Guid Id { get; set; }
public string Name { get; set; }
}
And here is some (simple) code that uses it:
foreach (var animal in db.Animals)
{
foreach (var trait in animal.Traits)
{
//animal.Traits count is 0, so this does not run.
//However there are traits in the database, as my populate
//function is working fine.
Console.WriteLine("{0} is {1}", animal.Name, trait.Name);
}
}
----Edit Answer Summary----
Using the article and information provided in the answers below, I was able to discover I could either eagerly load using db.Animals.Include() or enable lazy loading. There is a trick to enabling lazy loading and being able to use it though. First to enable lazy loading I added:
db.Configuration.LazyLoadingEnabled = true;
Next I changed my Traits collection in the following manner:
public virtual ObservableCollection<Trait> Traits { get; set; }
Making it virtual allows the automatically generated proxy to lazily load Traits. That's it! IMHO I think the MSDN docs should shout this load and clear in the POCO EF 4.2 coding conventions. Again thanks for the help.
There are a few reasons that your wire-up methods may appear to have no data. To load related data you need to :
explicity load the data
meet the lazy loading requirements, or
use eager loading using Include()
My guess is that you turned off the virtual proxies. There is more on the requirements here:
http://msdn.microsoft.com/en-us/library/dd456855.aspx
If you don't use lazy loading you have to explicitly tell EF to load the relation with the Include method:
foreach (var animal in db.Animals.Include(a => a.Traits))
{
foreach (var trait in animal.Traits)
{
//...
}
}
You can read more about eager loading in this article.

Categories