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.
Related
I have 3 tables: users, posts, and likes. I want to do a lambda expression to return an array that holds username, postText, and liked (true or false)
var myList = _context.Posts.Join(_context.Users,
post => post.UserID_FK,
user => user.ID,
(post, user) => new { Post = post, User = user })
.Join(
_context.Likes,
u => u.User.ID,
likes => likes.UserID,
(u, likes) => new PostDTO
{
ID = u.Post.ID,
username = u.Patient.UserName,
Text = u.Post.Text,
Likes = u.Post.Likes,
liked = (likes.PostID == u.Post.ID && likes.UserID == userModel.ID)}
.OrderByDescending(d => d.Date);
return myList;
My problem is with my code I am getting everything I want, but i am getting repeated records. I am trying to understand why I am getting duplicate records? I have searched lambda expressions and I can not figure out where my issue is.
I thank you guys in advance!
This occurs because like with an SQL query joining tables you are building a Cartesian for all combinations. As a simple example of a Post with 2 Likes where we run an SQL statement with an inner join:
SELECT p.PostId, l.LikeId FROM Posts p INNER JOIN Likes l ON p.PostId == l.PostId WHERE p.PostId = 1
we would get back:
PostId LikeId
1 1
1 2
Obviously, returning more data from Post and Like to populate a meaningful view, but the raw data will look to have a lot of duplication. This compounds with the more 1-to-many relationships, so you need to manually interpret the data to resolve that you are getting back one post with two Likes.
When you utilize navigation properties correctly to map the relationships between your entities, EF will do the heavy lifting to turn these Cartesian combinations back into a meaningful model structure.
For example, take the following model structure:
public class Post
{
public int PostId { get; set; }
public DateTime Date { get; set; }
// ... Other Post-related data fields.
public virtual User Patient { get; set; }
public virtual ICollection<Like> Likes { get; set; } = new List<Like>();
}
public class Like
{
public int LikeId { get; set; }
public virtual Post Post { get; set; }
public virtual User User { get; set; }
}
public class User
{
public int UserId { get; set; }
public string UserName { get; set; }
}
EF can likely work out all of these relationships automatically via convention, but I recommend being familiar with explicit configuration because there is always a time when convention fails in a particular situation where you want or need to use a different naming convention. There are four options for resolving relationships in EF: Convention, Attributes, EntityTypeConfiguration, and using OnModelCreating's modelBuilder. The below example outlines EntityTypeConfiguration via EF6. EF Code uses an interface called IEntityTypeConfiguration which works in a similar way but you implement a method for the configuration rather than call base-class methods in a constructor. Attributes and Convention are generally the least amount of work, but I typically run into situations they don't map. (They do seem to be more reliable with EF Core) Configuring via modelBuilder is an option for smaller applications but it gets cluttered very quicky.
public class PostConfiguration : EntityTypeConfiguration<Post>
{
public PostConfiguration()
{
ToTable("Posts");
HasKey(x => x.PostId)
.Property(x => x.PostId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(x => x.Patient)
.WithMany()
.Map(x => x.MapKey("PatientUserId"));
HasMany(x => x.Likes)
.WithRequired(x => x.Post)
.Map(x => x.MapKey("PostId"));
}
}
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
ToTable("Users");
HasKey(x => x.UserId)
.Property(x => x.UserId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
public class LikeConfiguration : EntityTypeConfiguration<Like>
{
public LikeConfiguration()
{
ToTable("Likes");
HasKey(x => x.LikeId)
.Property(x => x.LikeId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(x => x.User)
.WithMany()
.Map(x => x.MapKey("UserId"));
}
}
// In your DbContext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.AddFromAssembly(GetType().Assembly);
}
Now when you want to query your data, EF will manage the FK relationships and resolving any Joins and resulting Cartesian products. The question just becomes what data do you want out of the domain model?
var viewData = _context.Posts
.Select(p => new PostDTO
{
ID = p.PostID,
UserName = p.Patient.UserName,
Text = p.Text,
Likes = p.Likes
.Select(l => new LikeDTO
{
ID = l.LikeId
UserName = l.User.UserName
}).ToList(),
Liked = p.Likes.Any(l => l.User.UserId == userModel.UserId)
}).OrderByDescending(p => p.Date)
.ToList();
This is a rough guess based on your original query where you want the posts, patient name, likes, and an indicator whether the current user liked the post or not. Note there are no explicit joins or even eager loading. (Include) EF will build the necessary statement with just the columns you need to populate your DTOs and the IDs necessary to relate the data.
You should also avoid mixing DTOs with Entities when returning data. In the above example I introduced a DTO for the Like as well as the Post since we want to return some detail about the Likes from our domain. We don't pass back references to Entities because when these get serialized, the serializer would try and touch each property which can cause lazy loading to get triggered and overall would return more information than our consumer needs or should potentially see. With the relationships mapped and expressed through navigation properties EF will build a query with the required joins automatically and work through the returned data to populate what you expect to see.
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 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();
I've been using LinQ to SQL with EF for a while now and have just stumbled on some strange behaviour that hopefully someone may be able to explain for me.
I'm performing a LinQ query on a database context that has a POCO entity with virtual properties for related entities. I'm using a where clause to eliminate instances where that entity is null. I'm using Lazy Loading.
return this.runtimeContext.FatalExceptionLogs.Where(l => l.RuntimeToDesignJuicerRelationship != null);
What I'm finding is that when my query is evaluated LinQ to SQL seems to entirely ignore my condition that the virtual property is null as if I'd never included this check at all. Instead it returns all records in my dbset named FatalExceptionLogs.
Now I have a simple workaround for this which is to first load the data into memory using .ToList() then
This looks like so:
return this.runtimeContext.FatalExceptionLogs.ToList().Where(l => l.RuntimeToDesignJuicerRelationship != null);
Now the check is performed in memory and all instances where the virtual property is null are returned (because there is no corresponding record as the id which is used for the join is nullable) and we're all good.
I have also considered:
Checking if the id that is joined on is null but unfortunately I can't garauntee that the referential integrity of the table has been maintained as there is no foreign key applied urgh!.
Checking if there are any records in the other table with the matching id, but that could be rather inefficient.
So I have a way of working around this but I'd really like to understand why LinQ to Sql is doing this and what other options there are, can anyone help?
The full code if it helps is below though I've cut it down for this example:
The query:
return this.runtimeContext.FatalExceptionLogs.ToList().Where(l => l.RuntimeToDesignJuicerRelationship != null);
The entity:
public class FatalExceptionLog
{
public int Id { get; set; }
public int? RuntimeJuicerId { get; set; }
public virtual RuntimeToDesignJuicerRelationship RuntimeToDesignJuicerRelationship { get; set; }
}
The mapping:
public class FatalExceptionLogMap : EntityTypeConfiguration<FatalExceptionLog>
{
public FatalExceptionLogMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Table & Column Mappings
this.ToTable("FatalExceptionLogging");
this.Property(t => t.RuntimeJuicerId).HasColumnName("JuicerLiveID");
this.HasRequired(t => t.RuntimeToDesignJuicerRelationship)
.WithMany(t => t.FatalExceptionLogs)
.HasForeignKey(t => t.RuntimeJuicerId);
}
}
Why NOT just do the normal joining?
return this.runtimeContext.FatalExceptionLogs.Where(
l => runtimeContext.RuntimeJuicers.Any(
y => y.RuntimeJuicerId == l.RuntimeJuicerId
)
);