I'm having an issue with EF6 lazy loading. I've searched StackOverflow, but the other questions I've found doesn't fit my case.
I'm using the virtual keyword and my classes are public. LazyLoadingEnabled and ProxyCreationEnabled are both set to true.
When I load a course object from the db, presentationId is set to the correct id and presentation is null which is correct, because it hasn't been loaded yet.
When I pass the presentation property to the PresentationsController.ToDto() method it should be lazy loaded, but I get a null reference exception inside the method because it's still null.
I know that the relationships are working because when I force load the presentation property of a course in the Watch window with a break point at the public static CourseDto ToDto(Course item, DnbContext db) method it gets loaded. See images:
As you can see item.presentation is null:
When I manually evaluate db.courses.Find(257).presentation which is referencing the same presentation as the item object does, they're both loaded:
Here are my POCOs:
public abstract class BaseModel : ISoftDelete {
public int id { get; set; }
}
public class Course : BaseModel {
[Required]
public int presentationId { get; set; }
public virtual Presentation presentation { get; set; }
}
My Web API controller methods:
// GET api/Courses/5
public CourseDto GetCourse(int id) {
var item = db.courses.FirstOrDefault(x => x.id == id);
return ToDto(item, db);
}
public static CourseDto ToDto(Course item, DnbContext db) {
var dto = new CourseDto();
if (item.presentationId > 0) dto.presentation = PresentationsController.ToDto(item.presentation, db);
return dto;
}
Any ideas?
Entities must have explicitly declared public constructors if you want to use lazy loading via dynamic proxies. (If you have other with parameters)
public abstract class BaseModel : ISoftDelete {
public BaseModel() { }
public int id { get; set; }
}
public class Course : BaseModel {
public Course() { }
[Required]
public int presentationId { get; set; }
public virtual Presentation presentation { get; set; }
}
Related
Below is my base class for Domain entities:
public interface IBaseEntity
{
public int Id { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime UpdatedDate { get; set; }
}
public class BaseEntity : IBaseEntity
{
public int Id { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime UpdatedDate { get; set; }
}
public class ExternalSystem : BaseEntity
{
public string Name { get; set; }
public string ConnectionUrl { get; set; }
public ICollection<ExternalSystemRules> ExternalSystemRules { get; set; }
}
public ExternalSystemRules : BaseEntity
{
public string RuleName { get; set; }
public string ConfiguredBy { get; set; }
public int ExternalSystemId { get; set; }
public ExternalSystem ExternalSystem { get; set; }
public ICollection<TaskSchedular> TaskSchedulars { get; set; }
}
public class ExternalSystemConfiguration : IEntityTypeConfiguration<ExternalSystem>
{
public void Configure(EntityTypeBuilder<ExternalSystem> builder)
{
builder.ToTable("ExternalSystem");
builder.Property(e=>e.Id).HasColumnName("ExternalSystemId");
builder.HasKey(e=>e.Id);
}
}
public class ExternalSystemRulesConfiguration : IEntityTypeConfiguration<ExternalSystemRules>
{
public void Configure(EntityTypeBuilder<ExternalSystemRules> builder)
{
builder.ToTable("ExternalSystemRules");
builder.Property(e=>e.Id).HasColumnName("ExternalSystemRuleId");
builder.HasKey(e=>e.Id);
builder.HasOne(d=>d.ExternalSystem)
.WithMany(p=>p.ExternalSystemRules)
.HasForeignKey(p=>p.ExternalSystemId)
.HasConstraintName("FK_ExternalSystemRules_ExternalSystemId");
builder.Navigation(p=>p.ExternalSystem)
.IsRequired()
.AutoInclude();
}
}
public class MyDatabaseContext : DbContext
{
private readonly IConfiguration _configuration;
public MyDatabaseContext(IConfiguration configuration)
{
_configuration = configuration;
Database.EnsureCreated();
}
public DbSet<ExternalSystem> ExternalSystem {get; set; }
public DbSet<ExternalSystemRules> ExternalSystemRule {get; set; }
public void Save()
{
this.SaveChanges();
}
}
I already had existing database created so I created all this domain models and configuration based on existing database tables and relationships.
Now when I am trying to get list of ExternalSystems like below :
var myDatabaseContext = new MyDatabaseContext();
var externalSystems = myDatabaseContext.ExternalSystem.ToList();
This returns the list of ExternalSystems but my "ExternalSystemRules" navigation property is null. All the other related child entities are null as well.
Now, I don't want to explicitly keep on using .Include() to load related entities. I want to use default feature of entity framework core of eager loading other related entities when querying parent entity.
What might be the problem here?
Database table:
ExternalSystem:
ExternalSystemId(PK) Name ConnectionUrl
ExternalSystemRules:
ExternalSystemRuleId(PK) RuleName ConfiguredBy ExternalSystemId(F.K)
This isn't a problem with your configuration, but how you query your data. You should manually include the relational record / navigation properties, you are looking for:
var externalSystems = myDatabaseContext
.ExternalSystem
.Include(es => es.ExternalSystemRules)
.ToList();
I would advise against using AutoInclude, though (*). While it might not seem to make a difference, when you have tens or hundreds of objects to query, performance will degrade very rapidly, once your dataset grows. Everytime you query a set with AutoInclude enabled, you will get all its navigation properties with it, whether you need it, or not. In addition, this will also apply, to all entity-types derived from this entity. If you decide to use it anyway, you can disable it for single queries by using .IgnoreAutoIncludes().
A problem common to both approaches (Include(), as well as AutoInclude()) are hierachies. They will work for very simple models, but once you try to map a hierachy - think of something like a tree, where a Rule can have sub-rules of the same type, you might run into problems with self-referencing loops and need to manually project.
There is a nice article explaining the problem here: https://khalidabuhakmeh.com/ef-core-and-aspnet-core-cycle-issue-and-solution
A better way of querying your data, would be to use "view models", as to avoid returing unused or sensitive data to your clients.
public class ExternalSytemVm
{
public int Id {get; set;}
public IEnumerable<ExternalSystemRulesVm> Rules {get; set;}
/* ...*/
}
public class ExternalSytemRulesVm
{
public int Id {get; set;}
public string Name {get; set;}
/* ...*/
}
var externalSystems = myDatabaseContext
.ExternalSystem
.Select(es => new ExternalSystemVm {
Id = es.Id
Rules = es.ExternalSystemRules.Select(esr => {
/* ... */
})
})
.ToList();
(*) If you are absolutely sure, that you will always need all of your properties, with all of the navigation properties in each and every query in your application, this might be fine.
Documentation: https://learn.microsoft.com/en-us/ef/core/querying/related-data/eager
Add the AutoInclude() to your Fluent API configuration as follows (similar to what you already have for ExternalSystemRules):
public class ExternalSystemConfiguration : IEntityTypeConfiguration<ExternalSystem>
{
public void Configure(EntityTypeBuilder<ExternalSystem> builder)
{
builder.ToTable("ExternalSystem");
builder.Property(e=>e.Id).HasColumnName("ExternalSystemId");
builder.HasKey(e=>e.Id);
builder.Navigation(e => e.ExternalSystemRules)
.AutoInclude();
}
}
I'm working with Entity Framework 6.0.2 Code First with Sql Server.
I have a base class called Entity and since we can't extend enums I need to redefine a property type for another class called Company, so I'm using the new keyword to hide the base property and redefine it.
public interface IEntity
{
Guid Id { get; set; }
State State { get; set; }
}
public abstract class Entity : IEntity
{
public Guid Id { get; set; }
public State State { get; set; }
}
public enum State
{
Inactive = 0,
Active = 1
}
public class Company : Entity
{
public new CompanyState State { get; set; }
public string SomeOtherProp { get; set; }
}
public enum CompanyState
{
Inactive = 0,
Active = 1,
Extra = 2
}
The problem I get is when Entity Framework is trying to create the DbContext it crashes with this error: "The item with identity 'State' already exists in the metadata collection. Parameter name: item"
I have a workaround: I could change the State propery in Entity class to int and cast the appropriate enum to int, but I think I'll lose type safety / restriction that the enums have.
I'd like to change the metadata info to avoid this error but I don't know how.
This guy here found a solution for some similar problem.
Neither your, nor his solution is nice. It is and remains a hack.
I would go with the solution you already mentioned. Change the state to stateId. And add a State Property to your Entity:
public State State {get {return (State)stateId;}
In your Company override this Property with new:
public new CompanyState State {get {return (CompanyState )stateId;}
But I think best solution would be, to change your inheritance hierarchy. I think either your IEntity should not have a state, or your company should not inherit from Entity.
Just for exposing another way you could also use this model with an hidden backing field and not mapped states
public interface IEntity
{
int Id { get; set; }
State State { get; set; }
}
public abstract class Entity : IEntity
{
protected int InternalState { get; set; }
public int Id { get; set; }
[NotMapped]
public State State
{
get { return (State) InternalState; }
set { InternalState = (int) value; }
}
// Entity is not a POCO class because of this :(
// If we want to hide InternalState this is the only way to map it
public class EntityMap : EntityTypeConfiguration<Entity>
{
public EntityMap()
{
// Properties
Property(t => t.InternalState)
.HasColumnName("State");
}
}
}
public enum State
{
Inactive = 0,
Active = 1
}
public class Company : Entity
{
[NotMapped]
public new CompanyState State
{
get { return (CompanyState)InternalState; }
set { InternalState = (int)value; }
}
[MaxLength(50)]
public string SomeOtherProp { get; set; }
}
public class Employee : Entity
{
[MaxLength(50)]
public string SomeOtherProp { get; set; }
}
public enum CompanyState
{
Inactive = 0,
Active = 1,
Extra = 2
}
I am having some strange issues, I am attempting to pull a record out of the database and it seems like most of it is null even know if I manually look in the DB it's populated.
Model
public class AdminConfiguration : Entity // Entity is an abstract class containing an ID
{
public bool Authentication { get; set; }
public List<ApplicationConfiguration> ApplicationConfiguration { get; set; }
public List<LinksConfiguration> LinksConfiguration { get; set; }
public EmailConfiguration EmailConfiguration { get; set; }
public bool WakeOnLan { get; set; }
}
Basically any reference to another class is null The only thing that is populated is the WakeOnLan property.
Query
public AdminConfiguration Find(int id)
{
return Db.AdminConfiguration.Find(id);
}
I have a feeling I have a misunderstanding regarding how I set up the models. I am expecting the query to return me a fully populated AdminConfiguration object.
Try to set navigation properties as virtual to enable lazy loading:
public virtual List<ApplicationConfiguration> ApplicationConfiguration { get; set; }
Please refer to https://msdn.microsoft.com/en-us/data/jj193542.aspx
This enables the Lazy Loading feature of Entity Framework. Lazy
Loading means that the contents of these properties will be
automatically loaded from the database when you try to access them.
The best way to setup your model is:
public class AdminConfiguration : Entity // Entity is an abstract class containing an ID
{
public AdminConfiguration()
{
this.ApplicationConfigurations = new HashSet<ApplicationConfiguration>();
this.LinksConfigurations = new HashSet<LinksConfiguration>();
}
public bool Authentication { get; set; }
public EmailConfiguration EmailConfiguration { get; set; }
public bool WakeOnLan { get; set; }
// Navigation properties
public virtual ICollection<ApplicationConfiguration> ApplicationConfigurations { get; set; }
public virtual ICollection<LinksConfiguration> LinksConfigurations { get; set; }
}
In one of my projects I use Entity Framework with virtual navigation properties on the entities. This means the entities are loaded from the database or created with IDbSet<T>.Create() a DynamicProxy is returned. Because I only make navigation properties virtual, this proxy does lazy loading and no change tracking (all properties need to be virtual to get a change tracking proxy).
My assumption was that the DynamicProxy takes care of initializing virtual ICollection<T> properties, as it does when the entity is loaded from the database. But when I create a new entity using IDbSet<T>.Create(), these navigation properties remain null.
Then I tried to make all properties virtual so I get a DynamicProxy with change tracking and to my surprise these navigation properties are initialized.
See the following example:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
static class Program
{
static void Main()
{
using (var db = new BloggingContext())
{
var changeTrackingBlog = db.ChangeTrackingBlogs
.Create(); // returns a DynamicProxy
var changeTrackingBlogPostCount = changeTrackingBlog
.Posts
.Count; // Posts has type EntityCollection<Post>
var lazyLoadingBlog = db.LazyLoadingBlogs
.Create(); // returns a DynamicProxy
var lazyLoadingBlogPostCount = lazyLoadingBlog.Posts
.Count; // Posts == null
}
}
}
public class BloggingContext : DbContext
{
public IDbSet<Post> Posts { get; set; }
public IDbSet<ChangeTrackingBlog> ChangeTrackingBlogs { get; set; }
public IDbSet<LazyLoadingBlog> LazyLoadingBlogs { get; set; }
}
public class Post
{
[Key]
public int PostId { get; set; }
public virtual ChangeTrackingBlog ChangeTrackingBlog { get; set; }
public virtual LazyLoadingBlog LazyLoadingBlog { get; set; }
}
public class ChangeTrackingBlog
{
[Key]
public virtual int BlogId { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class LazyLoadingBlog
{
// Not all properties are virtual, so no Change tracking, just lazy loading
[Key]
public int BlogId { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
I hope someone can explain what's happening here.
The usual way to avoid the null reference is to initialize the collection in the constructor:
public LazyLoading()
{
Posts = new List();
}
I believe it may be better practise to use a backing field - something to do with anonymous constructors not being called in certain circumstances (serialization or something - apologies for vagueness). So I do this:
public class LazyLoadingBlog
{
private ICollection<Post> _Posts = new List<Post>();
public virtual ICollection<Post> Posts
{
get { return _Posts ; }
//protected set lets EF override for lazy loading
protected set { _Posts = value;
}
}
Unfortunately I can't explain why you don't get an error when you mark all the properties virtual...
I have an ASP.NET MVC 4 application using Entity Framework 5 under .NET 4.5. The problem I'm having is that when I insert a detached entity that was created on the front-end, the lazy loading is not working.
Here is my code to add (or update):
public static int PersistMergeEntity(EntityTwo entityTwo)
{
int entityId;
using (var _db = new EntityDb())
{
if (_db.EntityTwo.Any(e => e.EntityTwoId == entityTwo.EntityTwoId))
{
_db.Entry(entityTwo).State = EntityState.Modified;
}
else
{
_db.EntityTwo.Add(entityTwo);
}
_db.SaveChanges();
//_db.Entry(entityTwo).Reference(e => e.EntityOne).Load();
entityId = entityTwo.EntityOne.EntityId;
}
EntityBo.UpdateData(entityId);
return entityTwo.EntityTwoId;
}
Here are my entities:
public class EntityTwo
{
[Key]
[ForeignKey("EntityOne")]
public int EntityTwoId { get; set; }
public Decimal NbValue { get; set; }
public virtual EntityOne EntityOne { get; set; }
}
public class EntityOne
{
[Key]
[ForeignKey("EntityTwo")]
public int EntityOneId { get; set; }
[ForeignKey("Entity")]
public int EntityId { get; set; }
public CsMonthDomain CsMonth { get; set; }
public int NbYear { get; set; }
public Decimal NbValue { get; set; }
public virtual Entity Entity { get; set; }
public virtual EntityTwo EntityTwo { get; set; }
}
And Entity is another entity that I need to do calculation every time I update or add EntityTwo.
The code works when the commented line is uncommented. But if it is the way shown up there, lazy loading will not work and I'll get a null Exception.
Lazy loading is set to true and the entities are, supposedly, correct, since it works when I explicitly load the navigation property.
I'm sorry about the names, but unfortunately I cannot post the real code ;(
Lazy loading does not work because the entityTwo you pass into the method is (most likely) not a dynamic proxy which it has to be in order to make lazy loading work. The instance is probably created outside the method using entityTwo = new EntityTwo();. To create a proxy of an entity you would need a context instance available and then use
entityTwo = _db.EntityTwos.Create();
In my opinion using explicit loading (your commented line) is the best solution in this situation. It has the same costs of querying the database once per navigation property like lazy loading would have plus the additional benefit over lazy loading that you could project a selection of properties you only need from the related entity, for example:
entityId = _db.Entry(entityTwo).Reference(eTwo => eTwo.EntityOne).Query()
.Select(eOne => eOne.EntityId)
.Single();