I have a simple model and a simple query. I'm trying to get EF to eager load my navigation properties:
// Document object has a navigation property "DocType" of type DocType
// DocType object has a navigation property "Documents" of type ICollection<Documents> that is NOT virutal
context.Set<Document>().Where(d=>d.Id == id).Include(d=>d.DocType).SingleOrDefault();
The problem is that this doesn't actually eager load DocType. The stranger thing is that excluding the Include() call does load the DocType property, but as a second query.
I've looked all around and applied every fix I found:
Added a call to Include()
Removed virtual from both navigation properties
Any idea what's going on here? Is it possible to coerce EF to merge this into a single query that's eager loaded?
EDIT: This is my data model:
namespace Data.Models {
class Document {
public int Id { get; set;}
public int TypeId { get; set; }
public DocType DocType { get; set; }
}
class DocType {
public int Id { get; set; }
public string FullName { get; set; }
public ICollection<Document> Documents { get; set; }
}
}
namespace Data.Mappings {
internal class DocumentTypeConfiguration : EntityTypeConfiguration<Document> {
public DocumentTypeConfiguration() {
ToTable("ProsDocs");
HasKey(m => m.Id);
Property(m => m.Id)
.HasColumnName("ProsDocId")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(m => m.TypeId)
.HasColumnName("ProsDocTypeId")
.HasMaxLength(3);
HasRequired(d => d.DocType)
.WithMany(dt=>dt.Documents)
.WithForeignKey(d=>d.TypeId);
}
}
internal class DocTypeTypeConfiguration : EntityTypeConfiguration<DocType> {
public DocTypeTypeConfiguration() {
ToTable("DocType");
HasKey(m => m.Id);
Property(m => m.Id)
.HasColumnName("DocTypeId")
.HasMaxLength(4);
Property(m => m.FullName)
.HasColumnName("DocTypeDesc")
.HasMaxLength(255);
}
}
}
The oddest thing is that when I call:
context.Set<Document>().Find(id);
The DocType properties are populated, but EF does this by executing two separate queries. Is it possible to design this in such a way the EF understands that this can be accomplished with one query?
EDIT 2: This question seems to address the same problem, but only states the calling Include() fixes it, which is not working in my case.
To include navigation properties I use this syntax (with quotes in the Include):
context.Documents.Where(d=>d.Id == id).Include("DocType").SingleOrDefault();
In general case, using string instead of expression (as mention #vanraidex) is not a good practice. In general case. However, when using third party providers (e.g. Oracle Provider) it can be the only way to get correct sql (with joins).
So, if you using special Data Provider and .Include() method doesn't work, try to use string instead of expression.
context.Documents.Where(d=>d.Id == id).Include("DocType").SingleOrDefault();
Related
I don't think using TPH or TPT inheritance method influence on this.
My goal is to have a single method that load everything from the database with mixed types of entities that may have separate relationship depending on the type.
Let's take this code-first model (simplistic mock-up to represent my problem):
public abstract class Entity
{
public int ID { get; set; }
public string Name { get; set; }
}
public abstract class EntityWithInfo : Entity
{
public AdditionalInformation Info { get; set; }
}
public class DerivedEntityWithInfo : EntityWithInfo
{
}
public class DerivedEntityWithInfo2 : EntityWithInfo
{
}
public class DerivedEntityWithoutInfo : Entity
{
}
public class AdditionalInformation
{
public int ID { get; set; }
public int SomeProperty { get; set; }
}
And the Fluent API Configuration:
modelBuilder.Entity<Entity>()
.HasKey(e => e.ID)
.Map<DerivedEntityWithInfo>(m => m.Requires("Type").HasValue(1)
.Map<DerivedEntityWithInfo2>(m => m.Requires("Type").HasValue(2)
.Map<DerivedEntityWithoutInfo>(m => m.Requires("Type").HasValue(3);
modelBuilder.Entity<EntityWithInfo>()
.HasRequired(e => e.Info)
.WithRequiredPrincipal()
.Map(e => e.MapKey("Entity_FK"));
modelBuilder.Entity<AdditionalInformation>()
.HasKey(e => e.ID);
Or visually:
With the SQL schema being simple:
Table Entity with: Id, Type, Name
Table AdditionalInformation with: Id, SomeProperty, Entity_FK
Now, I want to be able to do something like:
context.Entity.Where(t => t.ID = 304 || t.ID = 512).ToList();
This gives me correctly the list of all entities, and properly typed. But, of course the Info property is always null. Disabling LazyLoading and removing virtual don't force to load it either, as I understood I absolutely need to have a .Include(t => t.Info) line in there.
I know I can call
context.Entity.OfType<EntityWithInfo>().Include(t => t.Info).Where(t => t.ID = 304 || t.ID = 512).ToList();
But then I will only get entities derived of EntityWithInfo, and not the DerivedEntityWithoutInfo ones.
So let's try with an union:
context.Entity.OfType<EntityWithInfo>().Include(t => t.Info).Cast<Entity>()
.Union(context.Entity.OfType<DerivedEntityWithoutInfo>().Cast<Entity>())
.Where(t => t.ID == 719 || t.ID == 402);
This does not work, it tells me "Entity does not declare navigation property Info".
And also, I guess this would create one hell of a SQL query.
In fact, the very reason why I'm on this is because of a very old project that was using LINQ2SQL doing the equivalent with "LoadWith" options generating an abusive SQL query (duplicate the relationship table in a join for every inherited type). Loading a single entity take more times than loading the entire table of thousands of elements raw without hierarchy. So I was trying to see if porting to EntityFramework would generate a more optimal SQL query.
So, is there a way to do this or we are simply trying to do something the wrong way? This doesn't seems to be a very popular method to have both inheritance, relationship on derived classes, and eager loading because I find pretty much no resource on that online.
At this point, any suggestion on how to create this object model from this database model would be appreciated. Thanks
This is now supported in EF Core 2.1. Now to see if the resulting query is not too much performance-hungry.
Given I have the following entity with multiple collection properties...
public class Parent
{
public virtual int Id { get; set;}
public virtual ICollection<FirstChild> FirstChildren { get; set; }
public virtual ICollection<SecondChild> SecondChildren { get; set; }
}
Is there a way I can simultaneously eager load both of these properties using fluent NHibernate? Or simply eager load everything associated to the Parent.
If I have the following as my mapping...
public ParentMapping()
{
Id(p => p.Id).GeneratedBy.Identity();
HasMany(p => p.FirstChildren)
.Table("FirstChildren")
.KeyColumn("Id")
.Inverse()
.Cascade.AllDeleteOrphan()
.Fetch.Join();
HasMany(p => p.SecondChildren)
.Table("SecondChildren")
.KeyColumn("Id")
.Inverse()
.Cascade.AllDeleteOrphan()
.Fetch.Join();
}
The mapping above results in the error:
'Cannot simultaneously fetch multiple bags'.
Using the Fetch.Join() in the mapping works if I use it on just one of the properties.
I am able to eager load everything by using ToFuture() queries, however, I would prefer to do this in the mapping.
You need to use ISet instead of ICollection to use that feature.
You can take a look here and here.
I have 2 classes like this:
Parent.cs
public class Parent
{
public int Id {get;set;}
public virtual ICollection<Child> Children { get; set; }
}
Child.cs
public class Child
{
public int Id {get;set;}
public ItemStatusType ItemStatusTyp { get; set; }
public int ParentId {get;set;}
[ForeignKey("ParentId")]
public virtual Parent Parent { get; set; }
}
ItemStatusType.cs
public enum ItemStatusType
{
Active = 1,
Deactive = 2,
Deleted = 3
}
What I want is to somehow retrieve always the active ones and not the deleted ones. Since I am not deleting the record physically, I'm merely updating the ItemStatusType to Deleted status.
So, when I say ParentObj.Children I only wish to retrieve the active ones without further using Where condition.
Here is so far what I've done but giving an exception on runtime that I stated afterwards:
public class ParentConfiguration : EntityTypeConfiguration<Parent>
{
public ParentConfiguration()
{
HasMany(c => c.Children.Where(p => p.ItemStatusTyp != ItemStatusType.Deleted).ToList())
.WithRequired(c => c.Parent)
.HasForeignKey(c => c.ParentId)
;
}
}
Runtime Exception:
The expression 'c => c.Children.Where(p => (Convert(p.ItemStatusTyp)
!= 3)).ToList()' is not a valid property expression. The expression
should represent a property: C#: 't => t.MyProperty' VB.Net:
'Function(t) t.MyProperty'.
I had to use ToList after the expression, otherwise it does not compile.
What is the proper what to do what I want?
Thanks in advance,
You cannot use Where or any other logic in fluent property mapping - that's just configuration.
Basically you cannot solve what you need in declarative way.
There are some workarounds which you can use for first-level entities, like implement your own extension(s) MySet<T> which will return .Set<T>.Where(x => x.ItemStatusType != ItemStatusType.Deleted) and use it everywhere, but that won't solve filtering issue for child collections.
You can go hard way and prepare a separate set of entities to use for selecting data, which basically should be based on database views or stored procedures; you will have to create separate view for every entity, so you will be able to combine selecting in any relations based on these views-entities.
For inserting though you will need to have entities mapped over "real" tables. No sure if it worth it but it might in some cases.
I'm having problems with having two foreign key references to the same table.
The foreign key id fields are populated but the navigation fields and lists (the Team fields) are not - they are both null.
My classes are:
public class Team
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Fixture> HomeFixtures { get; set; }
public virtual ICollection<Fixture> AwayFixtures { get; set; }
}
public class Fixture
{
public int Id { get; set; }
public int HomeTeamId { get; set; }
public int AwayTeamId { get; set; }
public Team HomeTeam { get; set; }
public Team AwayTeam { get; set; }
}
and my dbContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Team> Teams { get; set; }
public DbSet<Fixture> Fixtures { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Fixture>()
.HasOne(f => f.HomeTeam)
.WithMany(t => t.HomeFixtures)
.HasForeignKey(t => t.HomeTeamId)
.OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict);
modelBuilder.Entity<Fixture>()
.HasOne(f => f.AwayTeam)
.WithMany(t => t.AwayFixtures)
.HasForeignKey(t => t.AwayTeamId)
.OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict);
}
}
I have tried adding [ForeignKey()] attributes to the HomeTeam and AwayTeam properties but it has no effect.
I have also tried changing the OnModelCreating method to work the other way, i.e.
modelBuilder.Entity<Team>()
.HasMany(t => t.HomeFixtures)
.WithOne(f => f.HomeTeam)
.HasForeignKey(f => f.HomeTeamId)
.OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict);
and the same for away fixtures but this produces identical behaviour.
It doesn't seem to matter how I query but the simplest case is
Fixture fixture = await _context.Fixtures.SingleOrDefaultAsync(f => f.Id == id);
The returned fixture object contains team Ids that are valid and in the database but the Team objects are still not populated.
Has anyone any idea what I'm doing wrong?
This is a brand new project and a brand new database so there's no legacy code interfering.
I'm using Visual Studio 2017rc with Entity Framework Core.
At present EF Core does not support lazy loading. Tracking issue here
That means by default navigation properties will not be loaded and will remain null. As a work-around you can use eager loading or explicit loading patterns.
Eager Loading
Eager loading is pattern where you request for the referenced data you need eagerly while running the query using Include API. The usage is somewhat different from how it worked in EF6. To include any navigation, you specify the lambda expression (or string name) in include method in your query.
e.g. await _context.Fixtures.Include(f => f.HomeTeam).FirstOrDefaultAsync(f => f.Id == id);
Presently, filtered include is not supported so you can request to load navigation fully or exclude it. So the lambda expression cannot be complex. It must be simple property access. Also to load nested navigation, you can either chain them with property access calls (like a.b.c) or when its after collection navigation (since you cannot chain them) use ThenInclude.
e.g. await _context.Fixtures.Include(f => f.HomeTeam).ThenInclude(t=> t.HomeFixtures).FirstOrDefaultAsync(f => f.Id == id);
It would be good to remember that include represent a path of navigation from the entity type its being called on to populate all naviagations on the path. Often you may need to write repeated calls if you are including multiple navigations at 2nd or further level. That is just for syntax though and query will be optimized and will not do repeated work. Also with string include you can specify whole navigation path without needing to use ThenInclude. Since reference navigation can utilize join to fetch all data needed in single query, & collection navigation can load all related data in single query, eager loading is most performant way.
Explicit Loading
When you have loaded object in the memory and need to load a navigation, while lazy loading would have loaded it while accessing navigation property, in the absence of that you need to call Load method by yourself. These methods are defined on ReferenceEntry or CollectionEntry.
e.g.
Fixture fixture = await _context.Fixtures.SingleOrDefaultAsync(f => f.Id == id);
_context.Entry(fixture).Reference(f => f.HomeTeam).Load();
var team = await _context.Teams.SingleOrDefaultAsync(t => t.Id == id);
_context.Entry(team).Collection(f => f.HomeFixtures).Load();
For reference navigation you would need Reference on EntityEntry to get ReferenceEntry. For collection navigation equivalent method is Collection. Then you just call Load method on it to load the data in the navigation. There is also async version of LoadAsync if you need.
Hope this helps.
I'm building a menu system for a website in ASP.Net Core. Let's assume I have a couple of database tables, one for Pages and one for Articles, although it only really matters that they are different entities. Each of them have a Name and Permalink property.
In my menu, which I want to also store in the database, I want to refer to the Name and Permalink of each entity. I have devised a simple menu class/model structure as follows:
Abstract MenuItem
public abstract class MenuItem
{
[Key]
public int MenuItemId { get; set; }
public int MenuPosition { get; set; }
public abstract string Name { get; }
public abstract string Permalink { get; }
}
Concrete ArticleMenuItem
public class ArticleMenuItem : MenuItem
{
public ArticleMenuItem() {}
public ArticleMenuItem(Article article)
{
Article = article;
}
public string Name => Article.Name;
public string Permalink => Article.Permalink;
[Required]
public int ArticleId { get; set; }
[ForeignKey("ArticleId")]
public Article Article { get; set; }
}
Concrete PageMenuItem
public class PageMenuItem : MenuItem
{
public PageMenuItem() {}
public PageMenuItem(Page page)
{
Page = page;
}
public string Name => Page.Name;
public string Permalink => Page.Permalink;
[Required]
public int PageId { get; set; }
[ForeignKey("PageId")]
public Page Page{ get; set; }
}
I then override onModelCreating(ModelBuilder modelBuilder) for the relevant DbContext as I don't want to make the individual DbSet<T>'s available:
modelBuilder.Entity<PageMenuItem>();
modelBuilder.Entity<ArticleMenuItem>();
As well as add the relevant DbSet<T> for the menu:
public virtual DbSet<MenuItem> MenuItems { get; set; }
Add a couple of sample records to the database when the app loads (assume I've got some articles and pages initialised too):
List<MenuItem> items = new List<MenuItem>()
{
new PageMenuItem(pages[0]) { MenuPosition = 1 },
new ArticleMenuItem(articles[0]) { MenuPosition = 2 }
};
items.ForEach(item => context.MenuItems.Add(item));
A simple repository method to get the menu items from the database:
public IEnumerable<MenuItem> GetAllMenuItems() => _context.MenuItems;
With all this in place I was hoping that I could get the Name and Permalink for each item as follows (in a view, for instance):
#foreach (MenuItem item in Model)
{
#item.Name
}
Sadly, this results in a null object exception, and then I remembered EF Core doesn't support lazy loading. So I want to eagerly load the shadow properties, specifically the related entities, when I get the menu items in the repository.
There are two approaches to accessing shadow properties. The first approach I took updating my repository method looked like this:
public IEnumerable<MenuItem> GetAllMenuItems() => _context.MenuItems
.Include(item => context.Entry(item).Property("Page").CurrentValue)
.Include(item => context.Entry(item).Property("Article").CurrentValue)
This results in:
InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpression1' to type 'System.Linq.Expressions.MemberExpression'.
Casting to (Page) and (Aticle) respectively results in:
InvalidOperationException: The property expression 'item => Convert(value(InfoSecipediaWeb.Infrastructure.EntityFramework.ApplicationDbContext).Entry(item).Property("Page").CurrentValue)' is not valid. The expression should represent a property access: 't => t.MyProperty'.
The second method for accessing shadow properties only seems to enable accessing a single property value:
public static TProperty Property<TProperty>([NotNullAttribute] object entity, [NotNullAttribute][NotParameterized] string propertyName);
However, giving it a try:
public IEnumerable<MenuItem> GetAllMenuItems() => _context.MenuItems
.Include(item => EF.Property<Page>(item, "Page"))
.Include(item => EF.Property<Article>(item, "Article"));
Results in:
InvalidOperationException: The property expression 'item => Property(item, "Page")' is not valid. The expression should represent a property access: 't => t.MyProperty'.
I'd like to know whether it is possible to use shadow properties for navigation with an inheritance model? If so, how do I include the related entities so that it is accessible in my concrete MenuItem classes? e.g. for public string Name => Page.Name.
Unfortunately currently there is no syntax for eager loading derived class properties (note that they are different from shadow properties). This along with the lack of lazy loading leaves the explicit loading to be the only option. See for instance ef-core load collection property of nested tph inherited member how you can use it for a single item, for collection of items I'm afraid you have to materialize the result into a list, then using explicit loading of the concrete types and rely on EF navigation property fix up.
For your example it could be something like this:
public IEnumerable<MenuItem> GetAllMenuItems()
{
var menuItems = _context.MenuItems.ToList();
_context.MenuItems.OfType<ArticleMenuItem>().Include(e => e.Article).Load();
_context.MenuItems.OfType<PageMenuItem>().Include(e => e.Page).Load();
return menuItems;
}
Another workaround (did I say only one) is to use manual union query, which basically kills the TPH idea:
public IEnumerable<MenuItem> GetAllMenuItems() =>
_context.MenuItems.OfType<ArticleMenuItem>().Include(e => e.Article)
.AsEnumerable() // to avoid runtime exception (EF Core bug)
.Concat<MenuItem>(
_context.MenuItems.OfType<PageMenuItem>().Include(e => e.Page));