This is my situation, very much simplified.
My classes;
public class ClassBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}
public class ClassMiddle1 : ClassBase
{
}
public class ClassMiddle2 : ClassBase
{
public Guid Token { get; set; }
}
public class ClassA : ClassMiddle1
{
public string UserId { get; set; }
public string Username { get; set; }
}
public class ClassB : ClassMiddle2
{
public string Username { get; set; }
}
And my OnModelCreating;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ClassBase>()
.Map(m => {
m.Properties(p => new { p.Id});
m.ToTable("TableBase");
});
modelBuilder.Entity<ClassMiddle1>()
.Map<ClassMiddle1>(m =>
{
m.Properties(p => new { });
m.ToTable("TableBase");
});
modelBuilder.Entity<ClassMiddle2>()
.Map<ClassMiddle2>(m =>
{
m.Properties(p => new { p.Token });
m.ToTable("TableBase");
});
modelBuilder.Entity<ClassA>()
.Map<ClassA>(m =>
{
m.Properties(p => new
{
p.UserId,
p.Username
});
m.ToTable("TableA");
});
modelBuilder.Entity<ClassB>()
.Map<ClassB>(m =>
{
m.Properties(p => new
{
p.Username
});
m.ToTable("TableB");
}).Property(p => p.Username).HasColumnName("User");
}
This works fine but the Discriminator column is by default Discriminator, NVARCHAR(128). I read that it is possible to define this column myself using something like below.
m.Requires("ClassType").HasValue(1);
I turned my possibilities inside out but all times running into a dead end. Anyone having a suggestion how to do it?
I will end with another question. As our hierarchy pretty much are as above but even more derivated classes like C, D, E, F and so on to... say P. We found out that EF are making this incredibly big database query (~150K). Anyone else ran into this scenario?
I am hoping with changing Discriminator to at least minimize this. By that I say we have a very neat class hierarchy but an ugly query set.
Late answer how the actual solution went. Only writing it down here because the documentation around this was not that easy to find.
My solution ended up like below...
modelBuilder.Entity<ClassBase>()
.Map(m => {
...
m.Requires("Discriminator").HasValue(1)
});
Regarding your "incredibly big database query": There are indeed performance and query generation issues with TPT inheritance mapping. There still doesn't seem to be a fix for those problems, only this vague announcement (August 2010):
The good news is that we are working
on these issues so that EF no longer
generates unnecessary SQL. The bad
news is that it will take some time
before the fix is delivered in a
future release.
(Quote from the linked article above.)
Related
Does Entity Framework support generic relations?
E.g.
public class Comment<T> : Entity
where T : Entity
{
public string Text { get; set; }
public long EntityId { get; set; }
public T Entity { get; set; }
}
public class DocumentComment : Comment<Document> {
}
public class DeliveryComment : Comment<Delivery> {
}
UPDATE: I'll expand on my process as I tried this.
I tried creating configurations to make it work as I figured I needed to specify the types of variables but got an error when trying to add the migration.
public class CommentConfiguration<TU,T> : IEntityTypeConfiguration<TU>
where TU : Comment<T>
where T : Entity
{
public Configure(EntityTypeBuilder<TU> builder)
{
builder.ToTable(nameof(Comment));
builder.Property(x => x.Id);
builder.HasKey(x => x.Id);
builder.HasOne(x => x.Entity)
.WithMany()
.HasForeignKey(x => x.EntityId)
.OnDelete(DeleteBehavior.ClientCascade)
}
}
public class DocumentCommentConfiguration : CommentConfiguration<DocumentComment,Document>
{
public Configure(EntityTypeBuilder<DocumentComment> builder)
{
}
}
public class DeliveryCommentConfiguration : CommentConfiguration<DeliveryComment,Delivery>
{
public Configure(EntityTypeBuilder<DeliveryComment> builder)
{
}
}
Actually using conventions based setup worked just fine for me, so (at least based on provided info) you don't need to provide IEntityTypeConfiguration's.
If you still want to then (apart from the compilation issues, cause currently provided code is not compliable) you need to fix the table name in CommentConfiguration<TU,T> (by default you can't map different entity types to the same table). For example:
public class CommentConfiguration<TU,T> : IEntityTypeConfiguration<TU>
where TU : Comment<T>
where T : Entity
{
public Configure(EntityTypeBuilder<TU> builder)
{
builder.ToTable(typeof(TU).Name);
// ... rest of the config
}
}
Full code sample used for testing.
Also can be useful - inheritance in EF Core.
This may seem like a duplicate question EF Core One-to-Many relationship list returns null, but the answer to that question didn't help me. My situation:
public class Section
{
public int Id { get; set; }
// ...
public IEnumerable<Topic> Topics { get; set; }
}
public class Topic
{
public int Id { get; set; }
// ...
public int SectionId { get; set; }
public Section Section { get; set; }
}
But I have not implemented the OnModelCreating method in DbContext because in that case, errors occurs with users identity. There are topics in the database with the specified SectionId. But no matter how I try to get the section, I get null in the Topics property. For example:
var section = _dbContext.Sections.Include(s => s.Topics).FirstOrDefault(s => s.Id == id);
What is the reason for this problem? Have I declared something wrong? Or maybe there is a problem in creating a topic?
UPDATE
I tried to override the OnModelCreating method this way:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Section>()
.HasMany(s => s.Topics)
.WithOne(t => t.Section);
base.OnModelCreating(modelBuilder);
}
And this way:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Section>()
.HasMany(s => s.Topics)
.WithOne(t => t.Section)
.HasForeignKey(prop => prop.SectionId);
base.OnModelCreating(modelBuilder);
}
I also added the virtual attribute to the dependencies again. It did not help. Added a test migration (thought there might be something wrong with the database structure), but the migration was empty.
SOLUTION
As a result, I solved the problem with a crutch:
var section = _dbContext.Sections.Include(s => s.Topics).FirstOrDefault(s => s.Id == id);
if (section == null)
{
return Error();
}
section.Topics = _dbContext.Topics.Where(t => t.SectionId == section.Id).Include(t => t.Author).ToList();
foreach(var topic in section.Topics)
{
topic.Author = _dbContext.Users.FirstOrDefault(u => u.Id == topic.AuthorId);
topic.Posts = _dbContext.Posts.Where(t => t.TopicId == topic.Id).ToList();
}
As you can see, I had to explicitly get data from the dbContext and assign them to the appropriate properties. Include method calls can be deleted because they do not perform the desired action.
Several suggestions:
Try to make your Section define Topics as ICollection rather than IEnumerable and virtual so that they can be lazy loaded if necessary
public class Section
{
public int Id { get; set; }
// ...
public virtual ICollection<Topic> Topics { get; set; }
}
In your solution sample, you should be able to leverage EF Core's ThenInclude method to get the Sections, Topics, and Authors in one fell swoop:
var section = _dbContext.Sections
.Include(s => s.Topics)
.ThenInclude(t => t.Authors)
.FirstOrDefault(s => s.Id == id);
However to get the collection of authors and posts in the same child collection, you may want to consider a custom projection into a new type. EF Core 3.1 should piece all of this into a single query. Earlier versions of Core would break it apart into multiple database queries depending on the version and exact syntax you used. Something like:
var items =
from section in _dbContext.Sections
from topic in section.Topics
select new {
section.Name,
topic.Description,
Authors = topic.Authors.ToList(),
Posts = topic.Posts.ToList()
};
Following the guide on this link https://www.learnentityframeworkcore.com/lazy-loading
Install the Microsoft.EntityFrameworkCore.Abstractions package into the project containing your model classes:
[Package Manager Console]
install-package Microsoft.EntityFrameworkCore.Abstractions
[Dotnet CLI]
add package Microsoft.EntityFrameworkCore.Abstractions
Alter the principal entity to include
a using directive for Microsoft.EntityFrameworkCore.Infrastructure
a field for the ILazyLoader instance
an empty constructor, and one that takes an ILazyLoader as a parameter (which can be private, if you prefer)
a field for the collection navigation property
a getter in the public property that uses the ILazyLoader.Load method
using Microsoft.EntityFrameworkCore.Infrastructure;
public class Author
{
private readonly ILazyLoader _lazyLoader;
public Author()
{
}
public Author(ILazyLoader lazyLoader)
{
_lazyLoader = lazyLoader;
}
private List<Book> _books;
public int AuthorId { get; set; }
public List<Book> Books
{
get => _lazyLoader.Load(this, ref _books);
set => _books = value;
}
}
The solution in the answer will not work in case of many-to-many relationship.
It is just an illustratory example, I understand the relations in this example do not make sense perse, but it plots relations in a way I need the solution. So please do not comment about that.
I am searching for a solution in which I can ignore saving a navigational property;
public class ClassRoom {
public Guid Id { get; set; }
public Guid? ClassRoomInformationId { get; set; }
public virtual ClassRoomInformation { get; set; }
public virtual Collection<Student> Students { get; set;
}
public class Student {
public Guid Id { get; set; }
public Guid? ClassRoomId { get; set; }
public Guid? StudentInformationId { get; set; }
public virtual StudentInformation { get; set; }
}
public class StudentEntityConfiguration : IEntityTypeConfiguration<Student> {
public void Configure(EntityTypeBuilder<Student> builder) {
builder.ToTable("Student");
builder.HasKey(s => s.Id);
builder.HasOne(s => s.StudentInformation)
.WithOne()
.HasForeignKey<Student>(s => s.StudentInformationId);
}
}
public class ClassRoomEntityConfiguration : IEntityTypeConfiguration<ClassRoom> {
public void Configure(EntityTypeBuilder<ClassRoom> builder) {
builder.ToTable("ClassRoom");
builder.HasKey(c => c.Id);
builder.HasOne(c => c.ClassRoomInformation)
.WithOne()
.HasForeignKey<ClassRoom>(c => c.ClassRoomInformationId);
builder.HasMany(c => c.Students)
.WithOne()
.HasForeignKey(c => c.ClassRoomInformation);
}
}
To clearify my question (Using EF 2.2); I want to update the student through it's own StudentRepository. And when I save a classroom through the ClassRoomRepository and the student might change in any way, I do not want that change to be persisted (even though it is included to be able to 'view' the data).
I have tried to add the following to the ClassRoomEntityConfiguration:
//BeforeSaveBehavior neither works
builder.Property(c => c.Students).Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;
However this gives the following exception:
... Cannot be used as a property on ... because it is configured as a navigation.'
Another thing I tried is setting the componentmodel readonly attribute on the Students list in the ClassRoom. This seems to be ignored as well.
I call this the goldilocks problem. You have a hierarchy of objects (Customer, Order, OrderDetails) and you only want to save at "just the right level" of the object-graph.
A work around is to load the object......change only thing things that you care about, then save it.
In the below, I am NOT saving the inputItem.
I am using inputItem to set a small subset of the values of the foundEntity.
public async Task<MyThing> UpdateAsync(MyThing inputItem, CancellationToken token)
{
int saveChangesAsyncValue = 0;
MyThing foundEntity = await this.entityDbContext.MyThings.FirstOrDefaultAsync(item => item.MySurrogateKey == inputItem.MySurrogateKey, token);
if (null != foundEntity)
{
/* alter JUST the things i want to update */
foundEntity.MyStringPropertyOne = inputItem.MyStringPropertyOne;
foundEntity.MyStringPropertyTwo = inputItem.MyStringPropertyTwo;
this.entityDbContext.Entry(foundEntity).State = EntityState.Modified;
saveChangesAsyncValue = await this.entityDbContext.SaveChangesAsync(token);
/* an exception here would suggest another process changed the "context" but did not commit the changes (usually by SaveChanges() or SaveChangesAsync() */
if (1 != saveChangesAsyncValue)
{
throw new ArgumentOutOfRangeException(string.Format("The expected count was off. Did something else change the dbcontext and not save it? {0}", saveChangesAsyncValue), (Exception)null);
}
}
else
{
ArgumentOutOfRangeException argEx = new ArgumentOutOfRangeException(string.Format(" SAD FACE {0} ", entity.MyThingKey), (Exception)null);
this.logger.LogError(argEx);
throw argEx;
}
return foundEntity;
}
SIDE NOTE:
2.2 is no longer supported (see link below). Dot Net Core 2.2 End of Lifetime is listed as "December 23, 2019"
You should upgrade to 3.1 or downgrade to 2.1. (downgrading is counter intuitive I know).
See
https://dotnet.microsoft.com/platform/support/policy/dotnet-core
TL;DR: I'm having trouble with Polymorphic mapping. I've made a github repo with a test suite that illustrates my issue. Please find it here: LINK TO REPO
I'm working on implementing a save/load feature. To accomplish this, I need to make sure the domain model that I'm serializing is represented in a serialization-friendly way. To accomplish this I've created a set of DTOs that contain the bare-minimum set of information required to do a meaningful save or load.
Something like this for the domain:
public interface IDomainType
{
int Prop0 { get; set; }
}
public class DomainType1 : IDomainType
{
public int Prop1 { get; set; }
public int Prop0 { get; set; }
}
public class DomainType2 : IDomainType
{
public int Prop2 { get; set; }
public int Prop0 { get; set; }
}
public class DomainCollection
{
public IEnumerable<IDomainType> Entries { get; set; }
}
...and for the DTOs
public interface IDto
{
int P0 { get; set; }
}
public class Dto1 : IDto
{
public int P1 { get; set; }
public int P0 { get; set; }
}
public class Dto2 : IDto
{
public int P2 { get; set; }
public int P0 { get; set; }
}
public class DtoCollection
{
private readonly IList<IDto> entries = new List<IDto>();
public IEnumerable<IDto> Entries => this.entries;
public void Add(IDto entry) { this.entries.Add(entry); }
}
The idea is that DomainCollection represents the current state of the application. The goal is that mapping DomainCollection to DtoCollection results in an instance of DtoCollection that contains the appropriate implementations of IDto as they map to the domain. And vice versa.
A little extra trick here is that the different concrete domain types come from different plugin assemblies, so I need to find an elegant way to have AutoMapper (or similar, if you know of a better mapping framework) do the heavy lifting for me.
Using structuremap, I'm already able to locate and load all the profiles from the plugins and configure the applications IMapper with them.
I've tried to create the profiles like this...
public class CollectionMappingProfile : Profile
{
public CollectionMappingProfile()
{
this.CreateMap<IDomainType, IDto>().ForMember(m => m.P0, a => a.MapFrom(x => x.Prop0)).ReverseMap();
this.CreateMap<DtoCollection, DomainCollection>().
ForMember(fc => fc.Entries, opt => opt.Ignore()).
AfterMap((tc, fc, ctx) => fc.Entries = tc.Entries.Select(e => ctx.Mapper.Map<IDomainType>(e)).ToArray());
this.CreateMap<DomainCollection, DtoCollection>().
AfterMap((fc, tc, ctx) =>
{
foreach (var t in fc.Entries.Select(e => ctx.Mapper.Map<IDto>(e))) tc.Add(t);
});
}
public class DomainProfile1 : Profile
{
public DomainProfile1()
{
this.CreateMap<DomainType1, Dto1>().ForMember(m => m.P1, a => a.MapFrom(x => x.Prop1))
.IncludeBase<IDomainType, IDto>().ReverseMap();
}
}
public class DomainProfile2 : Profile
{
public DomainProfile2()
{
this.CreateMap<DomainType2, IDto>().ConstructUsing(f => new Dto2()).As<Dto2>();
this.CreateMap<DomainType2, Dto2>().ForMember(m => m.P2, a => a.MapFrom(x => x.Prop2))
.IncludeBase<IDomainType, IDto>().ReverseMap();
}
}
I then wrote a test suite to make sure that the mapping will behave as expected when its time to integrate this feature with the application. I found whenever DTOs were getting mapped to Domain (think Load) that AutoMapper would create proxies of IDomainType instead of resolving them to the domain.
I suspect the problem is with my mapping profiles, but I've run out of talent. Thanks in advance for your input.
Here's another link to the github repo
I stumbled across this question when looking in to a polymorphic mapping issue myself. The answer is good, but just another option if you'd like to approach it from the base mapping perspective and have many derived classes, you can try the following:
CreateMap<VehicleEntity, VehicleDto>()
.IncludeAllDerived();
CreateMap<CarEntity, CarDto>();
CreateMap<TrainEntity, TrainDto>();
CreateMap<BusEntity, BusDto>();
See the automapper docs for more info.
I spent a little time reorganizing the repo. I went as far as to mimic a core project and two plugins. This made sure that I wouldn't end up with a false-positive result when the tests finally started passing.
What I found was that the solution had two(ish) parts to it.
1) I was abusing AutoMapper's .ReverseMap() configuration method. I was assuming that it would perform the reciprocal of whatever custom mapping I was doing. Not so! It only does simple reversals. Fair enough. Some SO questions/answers about it:
1, 2
2) I wasn't fully defining the mapping inheritance properly. I'll break it down.
2.1) My DomainProfiles followed this pattern:
public class DomainProfile1 : Profile
{
public DomainProfile1()
{
this.CreateMap<DomainType1, IDto>().ConstructUsing(f => new Dto1()).As<Dto1>();
this.CreateMap<DomainType1, Dto1>().ForMember(m => m.P1, a => a.MapFrom(x => x.Prop1))
.IncludeBase<IDomainType, IDto>().ReverseMap();
this.CreateMap<Dto1, IDomainType>().ConstructUsing(dto => new DomainType1()).As<DomainType1>();
}
}
So now knowing that .ReverseMap() is not the thing to use here, it becomes obvious that the map between Dto1 and DomainType1 was poorly defined. Also, The mapping between DomainType1 and IDto didn't link back to the base IDomainType to IDto mapping. Also an issue. The final result:
public class DomainProfile1 : Profile
{
public DomainProfile1()
{
this.CreateMap<DomainType1, IDto>().IncludeBase<IDomainType, IDto>().ConstructUsing(f => new Dto1()).As<Dto1>();
this.CreateMap<DomainType1, Dto1>().IncludeBase<DomainType1, IDto>().ForMember(m => m.P1, a => a.MapFrom(x => x.Prop1));
this.CreateMap<Dto1, IDomainType>().IncludeBase<IDto, IDomainType>().ConstructUsing(dto => new DomainType1()).As<DomainType1>();
this.CreateMap<Dto1, DomainType1>().IncludeBase<Dto1, IDomainType>().ForMember(m => m.Prop1, a => a.MapFrom(x => x.P1));
}
}
Now each direction of the mapping is explicitly defined, and the inheritance is respected.
2.2) The most base mapping for IDomainType and IDto was inside of the profile that also defined the mappings for the "collection" types. This meant that once I had split up the project to mimic a plugin architecture, the tests that only tested the simplest inheritances failed in new ways - The base mapping couldn't be found. All I had to do was put these mappings into their own profile and use that profile in the tests as well. That's just good SRP.
I'll apply what I've learned to my actual project before I mark my own answer as the accepted answer. Hopefully I've got it and hopefully this will be helpful to others.
Useful links:
this
this one was a good refactoring exercise. I admittedly used it as a starting place to build up my example. So, thanks #Olivier.
Okay, I know I have to be doing something wrong here because the performance times I'm getting are so different its shocking. I've been considering using the code first option of entity in an existing project of mine so I've been trying to do some performance test just to see how it compares. I'm using MSpec to run the tests against a remote development database.
Here are my tests:
public class query_a_database_for_a_network_entry_with_linq : ipmanagement_object {
protected static NetINFO.IPM_NetworkMaster result;
Because of = () => {
var db = new NetINFODataContext();
result = db.IPM_NetworkMasters.SingleOrDefault(c => c.NetworkID == 170553);
};
It should_return_an_ipm_networkmaster_object = () => {
result.ShouldBeOfType(typeof(NetINFO.IPM_NetworkMaster));
};
It should_return_a_net_ou_object_with_a_networkid_of_4663 = () => {
result.IPM_OUIDMaps.First().NET_OU.NET_OUID.ShouldEqual(4663);
};
}
public class query_a_database_for_a_network_entry_with_entity_code_first : ipmanagement_object {
protected static NetInfo.Core.Models.CTP.IPM_NetworkMaster result;
Because of = () => {
NetInfo.Core.Models.CTP.NetInfoDb db = new NetInfo.Core.Models.CTP.NetInfoDb();
result = db.IPM_NetworkMasters.SingleOrDefault(c => c.NetworkID == 170553);
};
It should_return_an_ipm_networkmaster_object = () => {
result.ShouldBeOfType(typeof(NetInfo.Core.Models.CTP.IPM_NetworkMaster));
};
It should_return_a_net_ou_object_with_a_networkid_of_4663 = () => {
result.NET_OUs.First().NET_OUID.ShouldEqual(4663);
};
}
As you can see from the datacontext with linq-to-sql I can't access object directly that have a many to many relationship. I have to use the intermediate lookup table. Which is one of the things I like about Entity framework. However when I run these test the linq test never takes longer than 4 seconds to complete (database is remote). Where the entity test takes almost 8 seconds every time. Not for sure why there is such a huge difference?? Here is excerpts of my POCO classes and my dbcontext:
DbContext:
public class NetInfoDb : DbContext {
public NetInfoDb() : base("NetINFOConnectionString") { }
public DbSet<IPM_NetworkMaster> IPM_NetworkMasters { get; set; }
public DbSet<IPM_NetworkType> IPM_NetworkTypes { get; set; }
public DbSet<NET_OU> NET_OUs { get; set; }
protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder) {
modelBuilder.Entity<IPM_NetworkMaster>()
.HasMany(a => a.NET_OUs)
.WithMany(b => b.IPM_NetworkMasters)
.Map(m => {
m.MapRightKey(a => a.NET_OUID, "NET_OUID");
m.MapLeftKey(b => b.NetworkID, "NetworkID");
m.ToTable("IPM_OUIDMap");
});
}
}
IPM_NetworkMaster:
public class IPM_NetworkMaster {
public int NetworkID { get; set; }
<snip>
public virtual ICollection<NET_OU> NET_OUs { get; set; }
}
NET_OU:
public class NET_OU {
public int NET_OUID { get; set; }
<snip>
public virtual ICollection<IPM_NetworkMaster> IPM_NetworkMasters { get; set; }
}
As everyone has mentioned, you need to profile your queries. Assuming you are using SQL Server, you can just spool up SQL Server Profiler and compare the queries and execution plans.
As with any performance issue, you must measure first. With your scenario, you have to do more. You have to measure twice with each technology and make sure you are comparing apples to apples. If you can rule out the sql being generated you will then have to measure the application code, to possibly rule any bottlenecks there.
I suspect it will be the generated queries though.