I use EF core to create my tables (code first) and I'm encountering strange behaviour with my abstract base classes for some entities. I have the class ConfigurableDiscount
as an abstract base class and multiple classes inheriting from it, for example: AfterSalesCoverage
. The classes look like this:
public abstract class ConfigurableDiscount : TenantEntity, TraceChangesEntity
{
public DealType DealType { get; set; }
[Required]
[Column(TypeName = "decimal(18,4)")]
public decimal Discount { get; set; }
}
public class AfterSalesCoverage : ConfigurableDiscount
{
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
}
The TenantEntity
and TraceChangesEntity
are another abstract class and an empty interface, respectively.
public abstract class TenantEntity : BaseDeleteEntity
{
public Guid BusinessUnitId { get; set; }
public virtual BusinessUnit BusinessUnit { get; set; }
}
public abstract class BaseDeleteEntity : BaseEntity
{
public DateTime? DeletedOn { get; set; }
}
public abstract class BaseEntity
{
[Required]
public DateTime CreatedOn { get; set; }
public Guid CreatedById { get; set; }
public virtual User CreatedBy { get; set; }
public DateTime? ChangedOn { get; set; }
public Guid? ChangedById { get; set; }
public virtual User ChangedBy { get; set; }
}
So as you can see, all of these are abstract except the AfterSalesCoverage
.
If I try to build or migrate, I get the following error:
The entity type 'ConfigurableDiscount' requires a primary key to be defined.
If I do so (either defining a dummy key inside ConfigurableDiscount
or shifting the Id
from AfterSalesCoverage
to ConfigurableDiscount
) and try to migrate, I get the following error:
The filter expression 'entity => (entity.DeletedOn == null)' cannot be specified for entity type 'AfterSalesCoverage'. A filt er may only be applied to the root entity type in a hierarchy.
Which makes me wonder why the AfterSalesCoverage isn't the root entity? The filter expression is defined/configured in the DBContext.
EDIT:
The filter in the DBContext is called inside of OnModelCreating(ModelBuilder builder)
and looks like this:
private void SetSoftDeleteFilterQuery(ModelBuilder builder)
{
System.Collections.Generic.IEnumerable<Type> q = from t in Assembly.GetExecutingAssembly().GetTypes()
where t.IsClass && t.IsSubclassOf(typeof(BaseDeleteEntity))
select t;
foreach (Type type in q)
{
MethodInfo method = typeof(CPEDbContext).GetMethod("ApplySoftDeleteFilterQuery", BindingFlags.NonPublic | BindingFlags.Instance);
MethodInfo generic = method.MakeGenericMethod(type);
if (type.Name != "RequestWrapper" && type.Name != "TenantEntity" && type.Name != "BaseBenchmarkDiscount")
{
{
generic.Invoke(this, new[] { builder });
}
}
}
}
private void ApplySoftDeleteFilterQuery<Tdb>(ModelBuilder builder) where Tdb : BaseDeleteEntity
{
builder.Entity<Tdb>().HasQueryFilter(entity => entity.DeletedOn == null);
}
It seems like this filter is the center of interest in this case...
Question:
Why does the ConfigurableDiscount
need a primary key? It's not supposed to be an actual entity. If a inherit directly from TenantEntity
, I don't need an ID in the TenantEntity either... The ConfigurableDiscount
entity is referenced nowhere but in the inheriting classes. So there is no DBSet or anything like that in my DBContext.