EF Core Include navigation property on abstract derived class - c#

I'm getting the following exception thrown when attempting to include a navigation property for a derived type:
System.InvalidOperationException: Invalid include.
at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.PopulateIncludeTree(IncludeTreeNode includeTreeNode, Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.ProcessInclude(NavigationExpansionExpression source, Expression expression, Boolean thenInclude)
at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
...
Using the following simplified class structure:
public abstract class BaseJob
{
public int Id { get; set; }
}
public abstract class WorkerJob : BaseJob
{
public Person Person { get; set; }
}
public class PaintingJob : WorkerJob
{
public string Color { get; set; }
}
public class Person {
public int Id { get; set; }
public string Name { get; set; }
}
When querying via the DbContext as documented here:
// Throws InvalidOperationException
var jobs = await _context.BaseJobs
.Include(job => (job as WorkerJob).Person) // WorkerJob is abstract
.ToListAsync();
This must be related to the navigation property existing on an abstract class as adjusting the query to the following works as intended:
// Works as intended
var jobs = await _context.BaseJobs
.Include(job => (job as PaintingJob).Person) // PaintingJob is concrete
.ToListAsync();
However, I have many concrete derived classes and I don't want to have to change the query every time a new concrete class is added. Could this be a bug with EF Core, or am I doing something wrong?

Absolutely not a bug, the error was mine. I had misconfigured the context as follows:
modelBuilder.Entity<PaintingJob>(entity =>
{
entity.HasBaseType<WorkerJob>();
entity.HasOne(x => x.Person)
.WithMany()
.HasForeignKey(x => x.PersonId);
});
Changing this to use the abstract class when defining the navigation property resolves the exception being thrown and returns results as expected.
modelBuilder.Entity<WorkerJob>(entity =>
{
entity.HasBaseType<BaseJob>();
entity.HasOne(x => x.Person)
.WithMany()
.HasForeignKey(x => x.PersonId);
});

Related

Why doesn't Automapper inherit ForPath-ignores?

We are mapping domain hierarchy to the Dto hierarchy. Some of the properties of the domain base class get flattened. We are using ReverseMap to simplify the mapping back to domain from dto.
We've mapped Contained.Id do ContainedId of Dto and ignored the path for the reverse map.
What we were expecting, that mapping back to domain object shouldn't create new instances for Contained-property. But that was not the case.
After some investigations we've found out, that it was due to not-inheriting the .ForPath(, opt => opt.Ignore()) for the ReverseMap.
We are using 9.0.0 version of Automapper.
Here is the code:
public class Contained {
public Guid Id { get; set; }
}
public class Base {
public Contained Contained { get; set;}
}
public class Derived: Base { }
public abstract class BaseDto {
public Guid? ContainedId { get; set; }
}
public class DerivedDto: BaseDto { }
[Test]
public void ForPathInheritanceWorks()
{
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Base, BaseDto>()
.IncludeAllDerived()
.ReverseMap()
.ForPath(d => d.Contained.Id, opt => opt.Ignore());
cfg.CreateMap<Derived, DerivedDto>()
.ReverseMap();
});
var mapper = configuration.CreateMapper();
var derived = mapper.Map<Derived>(new DerivedDto());
Assert.That(derived.Contained, Is.Null); // fails, because derived.Contained is not null.
}
To workaround an issue we had to add
.ForPath(d => d.Contained.Id, opt => opt.Ignore())
after ReverseMap of the derived class.
For me it looks like a bug in Automapper.

EF Core Many to Many Relationship Composite key creation shows invalid Property Expression

I am following the guide on msdn : https://learn.microsoft.com/en-us/ef/core/modeling/relationships#other-relationship-patterns
Two classes between which i need many to many relatioship:
public class Initiative
{
public int InitiativeId { get; set; }
//other properties
}
public class AppUser : IdentityUser
{
//properties
}
My Join class :
public class UserVolunteer
{
public string userId;
public AppUser user;
public int initiativeId;
public Initiative initiative;
}
My fluent api code :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserVolunteer>()
.HasKey(t => new { t.userId, t.initiativeId });
modelBuilder.Entity<UserVolunteer>()
.HasOne(bc => bc.user)
.WithMany(b => b.UserVolunteers)
.HasForeignKey(bc => bc.userId);
modelBuilder.Entity<UserVolunteer>()
.HasOne(bc => bc.initiative)
.WithMany(c => c.UserVolunteers)
.HasForeignKey(bc => bc.initiativeId);
}
while initializing migrations, i get the following error:
The properties expression 't => new <>f__AnonymousType0`2(userId = t.userId, initiativeId = t.initiativeId)' is not valid. The expression should represent a property access: 't => t.MyProperty'. When specifying multiple properties use an anonymous type: 't => new { t.MyProperty1, t.MyProperty2 }'.
Parameter name: propertyAccessExpression
I dont get whats the problem. I am doing the same as in guide and as in error.
In class UserVolunteer, userId and initiativeId is a public field and not a property. Therefore it throws exception on t => new { t.userId, t.initiativeId } because it is not a valid property access.
public class UserVolunteer
{
public string userId { get; set; }
public AppUser user { get; set; }
public int initiativeId { get; set; }
public Initiative initiative { get; set; }
}

Automapper: ignore property on inherited class

I have a source class like this:
public class Basket {}
a target class like this:
public class BasketModel
{
public string Property { get; set; }
}
and a mapping like this:
Mapper.CreateMap<Basket, BasketModel>()
.ForMember(x => x.Property, o => o.ResolveUsing(x => "anything"));
Now I've made the "Property" property in the original model virtual and created a new class that inherits from the model:
public class BasketModel
{
public virtual string Property { get; set; }
}
public class BasketModel2 : BasketModel
{
public override string Property
{
get
{
return "some value";
}
}
}
I've updated the mapping as such:
Mapper.CreateMap<Basket, BasketModel>()
.ForMember(x => x.Property, o => o.ResolveUsing(x => "anything"))
.Include<Basket, BasketModel2>();
And created the mapping
Mapper.CreateMap<Basket, BasketModel2>()
.ForMember(x => x.Property, o => o.Ignore());
Now when I try to map into BasketModel2, instead of null, the value of Property is "anything".
What am I missing here?
Ok I think I had a brainfart when I wrote this code. model2.Property is never going to be null because it's a getter that always returns the same string. I have some refactoring to do, but AutoMapper was doing what it needs to do, I was just using it wrong.

Ignore Inherited Property EF6

TB_R_LEAVE_Configuration
public class TB_R_LEAVE_REQ_ID_Configuration : DataUseLogConfiguration<Entities.TB_R_LEAVE_REQ_ID>
{
public TB_R_LEAVE_REQ_ID_Configuration()
{
ToTable("TB_R_LEAVE_REQ_ID");
HasKey(e => e.LEAVE_REQ_ID);
HasRequired<TB_R_LEAVE_REQ>(e => e.TB_R_LEAVE_REQ);
Property(e => e.LEAVE_REQ_ID).HasColumnName("LEAVE_REQ_ID").HasMaxLength(10);
Property(e => e.LEAVE_ID).HasColumnName("LEAVE_ID").HasMaxLength(5);
}
}
DataUseLogConfiguration (abstract base class)
public abstract class DataUseLogConfiguration<T> : EntityTypeConfiguration<T> where T : DataUseLog
{
public DataUseLogConfiguration()
{
Property(e => e.CREATED_BY).HasColumnName("CREATED_BY").HasMaxLength(5).IsRequired();
Property(e => e.CREATED_DT).HasColumnName("CREATED_DT").IsRequired();
Property(e => e.UPDATED_BY).HasColumnName("UPDATED_BY").HasMaxLength(5).IsOptional();
Property(e => e.UPDATED_DT).HasColumnName("UPDATED_DT").IsOptional();
}
}
I want to ignore all DataUseLogConfiguration properties
I tried use Ignore on TB_R_LEAVE_Configuration:
Ignore(e => e.UPDATED_BY);
and this happen:
"The property 'UPDATED_BY' is not a declared property on type
'TB_R_LEAVE_REQ_ID'. Verify that the property has not been explicitly
excluded from the model by using the Ignore method or
NotMappedAttribute data annotation. Make sure that it is a valid
primitive property."
Without Ignore this happen:
"ORA-00904: \"Extent2\".\"UPDATED_BY\": invalid identifier"
The Data Model also use inherited base class
public class TB_R_LEAVE_REQ_ID : DataUseLog
{
public string LEAVE_REQ_ID { get; set; }
public string LEAVE_ID { get; set; }
public virtual TB_M_LEAVE TB_M_LEAVE { get; set; }
public virtual TB_R_LEAVE_REQ TB_R_LEAVE_REQ { get; set; }
}
How i properly ignore properties on base class just using fluent API on TB_R_LEAVE_Configuration (im try to avoid changing the data model)
thanks for everyone who try to help me, btw this is how i solve it with just edit the TB_R_LEAVE_REQ_ID_Configuration
public class TB_R_LEAVE_REQ_ID_Configuration : EntityTypeConfiguration<Entities.TB_R_LEAVE_REQ_ID>
{
public TB_R_LEAVE_REQ_ID_Configuration()
{
ToTable("TB_R_LEAVE_REQ_ID");
HasKey(e => e.LEAVE_REQ_ID);
HasRequired<TB_R_LEAVE_REQ>(e => e.TB_R_LEAVE_REQ);
Ignore(e => e.UPDATED_BY);
Ignore(e => e.UPDATED_DT);
Ignore(e => e.CREATED_BY);
Ignore(e => e.CREATED_DT);
Property(e => e.LEAVE_REQ_ID).HasColumnName("LEAVE_REQ_ID").HasMaxLength(10);
Property(e => e.LEAVE_ID).HasColumnName("LEAVE_ID").HasMaxLength(5);
}
}
notice that i changed the inheritance to EntityTypeConfiguration then just add the ignores as usual
Hope this help everyone in the future

TPC in Entity Framework v6

I'm trying to do a pretty simple thing in Entity Framework.
I have a product that has zero or more parameters and these parameters will be mapped to their own tables. However, I'm unable to get this to work. I've been trying to get the mappings right and then use the migrations to see what the database is supposed to look like. I know that this is very simple in NHibernate, but I'm forced against my will to use Entity Framework v6.
Background
These are my entities:
namespace Entities
{
public class EntityState
{
public int Id { get; set; }
}
public class ProductState : EntityState
{
public virtual ICollection<ProductParameterState> Parameters { get; set; }
}
public abstract class ProductParameterState : EntityState
{
}
public class ColorParameterState : ProductParameterState
{
public virtual string Color { get; set; }
}
public class SizeParameterState : ProductParameterState
{
public virtual int Size { get; set; }
}
}
I would like to store this in the following schema:
How to do this?
My attempts
Table-per-class
I tried mapping using TPC:
namespace Mappings
{
public class ProductMap : EntityTypeConfiguration<ProductState>
{
public ProductMap()
{
HasKey(x => x.Id);
Property(x => x.Name);
HasMany(x => x.Parameters);
}
}
public class ColorParameterMap : EntityTypeConfiguration<ColorParameterState>
{
public ColorParameterMap()
{
HasKey(x => x.Id);
Property(x => x.Color);
Map(x =>
{
x.ToTable("ColorParameters");
x.MapInheritedProperties();
});
}
}
public class SizeParameterMap : EntityTypeConfiguration<SizeParameterState>
{
public SizeParameterMap()
{
HasKey(x => x.Id);
Property(x => x.Size);
Map(x =>
{
x.ToTable("SizeParameters");
x.MapInheritedProperties();
});
}
}
}
But this gives the error The association 'ProductState_Parameters' between entity types 'ProductState' and 'ProductParameterState' is invalid. In a TPC hierarchy independent associations are only allowed on the most derived types..
Don't use an inheritence strategy
So I tried to remove the MapInheritedProperties, but then it wants to create an additional, and unwanted, table:
CreateTable(
"dbo.ProductParameterStates",
c => new
{
Id = c.Int(nullable: false, identity: true),
ProductState_Id = c.Int(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.ProductStates", t => t.ProductState_Id)
.Index(t => t.ProductState_Id);
I don't want this. I am able to get rid of this one by removing the Parameters property in Product, but then I'm not able to use the Parameters of a Product.
Am I asking for too much or is it possible?
You can use TPC, but the relationship must be bidirectional with explicit FK defined (which I guess is the opposite of "independent association" mentioned in the error message).
Add inverse navigation property and FK property to your base entity:
public abstract class ProductParameterState : EntityState
{
public int ProductId { get; set; }
public ProductState Product { get; set; }
}
and use the same entity configurations as in your first attempt, except for the ProductMap where you either remove the following
HasMany(x => x.Parameters);
or change it to
HasMany(e => e.Parameters)
.WithRequired(e => e.Product)
.HasForeignKey(e => e.ProductId);

Categories