EF Code First Lazy loading Not Working - c#

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.

Related

Related data not saved when added after creation with EF Core 6

I have POCO objects that are exposed through a repository that uses EF Core 6 to access a database. I can persist "parent" objects to the database and related data that is added to the parent object before creating is persisted successfully as well. However, when trying to add children (SingleSimulationResult objects) to a parent object (SingleSimulation objects) after it has been created, the children are not persisted to the database.
Here is the code that tries to add and save children to the parent object.
singleSim.AddResultsToSimulation(allResults);
Console.WriteLine($"# results: {singleSim.Results.Count}"); // # results: 2
await scopedRepository.Save();
var test = await scopedRepository.GetById(singleSim.Id);
Console.WriteLine($"# results test: {test.Results.Count}"); // # results: 0
SingleSimulation class (BaseEntity just defines an Id property):
public class SingleSimulation : BaseEntity, IAggregateRoot
{
public string Name { get; private set; }
public string Description { get; private set; }
public double Capital { get; private set; }
public List<List<double>> Returns { get; private set; }
private readonly List<SingleSimulationStrategy> _strategies = new List<SingleSimulationStrategy>();
public IReadOnlyCollection<SingleSimulationStrategy> Strategies => _strategies.AsReadOnly();
private List<SingleSimulationResult> _results = new List<SingleSimulationResult>();
public IReadOnlyCollection<SingleSimulationResult> Results => _results.AsReadOnly();
public SingleSimulation()
{
}
public SingleSimulation(string name, string description, double capital, List<List<double>> returns, List<SingleSimulationStrategy> strategies)
{
Name = name;
Description = description;
Capital = capital;
Returns = returns;
_strategies = strategies;
}
public void AddResultsToSimulation(List<SingleSimulationResult> results)
{
if (_results is null)
return;
foreach (var result in results)
{
_results.Add(result);
}
}
}
Repository class:
public class SingleSimulationRepository : ISingleSimulationRepository
{
private SimulationDbContext _dbContext;
public SingleSimulationRepository(SimulationDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task Add(SingleSimulation entity)
{
await _dbContext.AddAsync(entity);
}
public async Task<SingleSimulation> GetById(int id)
{
return await _dbContext.SingleSimulations.FindAsync(id);
}
...
public async Task Save()
{
await _dbContext.SaveChangesAsync();
}
}
DbContext:
public class SimulationDbContext : DbContext
{
public SimulationDbContext(DbContextOptions<SimulationDbContext> options)
: base(options)
{
}
public DbSet<SingleSimulation> SingleSimulations { get; set; }
public DbSet<SingleSimulationResult> SingleSimulationResults { get; set; }
public DbSet<SingleSimulationStrategy> SingleSimulationStrategies { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Seed data and custom conversion functions
}
}
Here's what I have tried (to no avail):
Using Fluent API to configure One-to-Many relationship for Results (using .HasMany()).
modelBuilder.Entity<SingleSimulation>()
.HasMany(x => x.Results)
.WithOne();
Using AddRange() to add result objects to the DB before adding them to the parent and finally saving to DB (SaveChangesAsync).
Using Attach() to start tracking result objects before adding them to the parent.
Using Include() when loading the parent object from the database before adding children and trying to save them.
It feels like I'm missing something small, but after scouring the docs and other sources I cannot find the problem. What do I need to do to get children added to the parent after the parent has already been created to actually save to the DB?
After debugging by printing the EF Core change tracker's LongView, I noticed that no changes are detected on the object (even if changing a simple string property). It turns out the problem was that the singleSim object I was modifying was returned from a different dbContext than the one used by the scopedRepository.
The model setup wasn't the problem after all. Even without the Fluent API config the setup works as intended (even with the read only collections and private backing fields).

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

Losing connection when returning entity with navigational properties using Include(...)

After quite some digging and confusing error messages, I've arrived at this sample (which I believe to be the smallest example reproducing the issue). I can almost certainly conclude that the issue appears due to the entity linked to the one that I'm returning.
[OperationContract]
[WebGet(UriTemplate = "Stations")]
List<Station> GetStations();
public List<Station> GetStations()
{
List<Station> stations = new List<Station>();
using (Context context = new Context())
foreach (Station station in context.Stations.Include(element => element.Records))
//stations.Add(station.Copy());
stations.Add(station);
return stations;
}
It works if I activate the line with Copy() (which creates a new instance of a station and copies over all the properties except for the records, which it creates by itself). However, when I just add the stations without creating a copy (regardless of whether I keep the records, nullify them or set en empty list), it doesn't roll well.
Since I used Include(), the objects being disposed isn't the issue anymore. The error message I get says in the console like so.
http://localhost:25760/MyService.svc/Stations net::ERR_CONNECTION_RESET
Googling that gave me a lot of references to Apache (I'm running it on IIS), PHP (I'm building it on .NET) and security issues with certificates (I'm not using any and the other calls work well).
So my suspicion is either that the error message is misleading coming from a confused computer or that I'm missing something in my setup. The autogenerated classes reflect the foreign key I added to the tables and look like this.
alter table Records
add constraint FkStationId
foreign key (StationId)
references Stations (Id)
public partial class Record
{
public System.Guid Id { get; set; }
public Nullable<System.Guid> StationId { get; set; }
...
public virtual Station Station { get; set; }
}
public partial class Station
{
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Station() { this.Records = new HashSet<Record>(); }
public System.Guid Id { get; set; }
...
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Record> Records { get; set; }
}
I see nothing that leads me to any idea on how to trouble-shoot it. This error shouldn't happen. On the other hand - Fukushima shouldn't happen neither. But it did.
Finally I got this running.
Turn off proxy creation.
Turn off lazy loading.
Make virtual property ignorable to serialization.
Load in the linked entity explicitly.
The first two items are done in the constructor of the context.
public partial class Context : DbContext
{
public Context() : base("name=ContextConnection")
{
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
...
}
The third is done by attributing one of the virtual properties that lead to circular dependency as not valid for serialization.
public partial class Station
{
...
[IgnoreDataMemeber]
public virtual ICollection<Record> Records { get; set; }
}
The last one is including or omitting the navigational property to be (or not to be displayed). In this case, it made sense to jam in the information about stations into each record. The stations can be presented without the records, though.
public List<Station> GetStations()
{
using (Context context = new Context())
return context.Stations
.ToList();
}
public List<Record> GetRecords()
{
using (Context context = new Context())
return context.Records
.Include(record => record.Station)
.ToList();
}
Having said that, there will be dragons. This approach leads to a lot of work as it requires to manually re-edit the auto-generated files each time they're re-created. So i went with Code First, instead.

eager loading with dbcontext and projection

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);

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