Я использую ядро EF для создания своих таблиц (сначала код) и сталкиваюсь со странным поведением с моими абстрактными базовыми классами для некоторых сущностей. У меня есть класс ConfigurableDiscount
в качестве абстрактного базового класса и несколько классов, унаследованных от него, например: AfterSalesCoverage
. Классы выглядят так:
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; }
}
TenantEntity
и TraceChangesEntity
- это еще один абстрактный класс и пустой интерфейс соответственно.
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; }
}
Итак, как вы можете видеть, все они абстрактны, кроме AfterSalesCoverage
.
Если я пытаюсь построить или перенести, я получаю следующую ошибку:
Тип сущности 'ConfigurableDiscount' требует определения первичного ключа.
Если я сделаю это (определив фиктивный ключ внутри ConfigurableDiscount
или переместив Id
с AfterSalesCoverage
на ConfigurableDiscount
) и попытаюсь выполнить миграцию, я получу следующую ошибку:
Выражение фильтра 'entity => (entity.DeletedOn == null)' не может быть указано для типа объекта AfterSalesCoverage. Фильт
Это может быть применено только к корневому типу сущности в иерархии.
Что заставляет меня задуматься, почему AfterSalesCoverage не является корневым объектом? Выражение фильтра определяется / настраивается в DBContext.
EDIT:
Фильтр в DBContext вызывается внутри OnModelCreating(ModelBuilder builder)
и выглядит следующим образом:
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);
}
Похоже, этот фильтр является центром интереса в этом случае ...
Вопрос:
Зачем ConfigurableDiscount
нужен первичный ключ? Это не должно быть реальной сущностью. Если наследование происходит непосредственно от TenantEntity
, мне также не нужен идентификатор в TenantEntity ... На сущность ConfigurableDiscount
ссылаются нигде, кроме как в наследующих классах. Так что в моем DBContext нет DBSet или чего-то подобного.