【发布时间】:2021-02-04 17:57:42
【问题描述】:
我有一个通用的 EF BaseEntityConfiguration 类,用于设置基本属性(如主键、用于软删除的属性、查询过滤器等)以及存储 System.Type 和一个实体的派生配置JSON 属性。如果我不使用泛型类而只实现 IEntityTypeConfiguration 则值转换有效并且没有错误。但是,如果我从基类继承,我会遇到关于保存类型和对象而不进行任何转换的 EF Core 问题。从基类继承且不需要转换的其他配置工作正常。
错误:
Error: The property 'MessageLog.Data' could not be mapped because it is of type 'object', which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
public class MessageLogConfiguration
//: IEntityTypeConfiguration<MessageLog>
: BaseEntityConfiguration<MessageLog, int>
{
public MessageLogConfiguration(ILogger<MessageLogConfiguration> logger)
: base(logger)
{ }
public override void Configure(EntityTypeBuilder<MessageLog> builder)
{
base.Configure(builder);
//builder
// .HasKey(x => x.Id);
builder
.Property(m => m.MessageId)
.IsRequired();
builder
.Property(m => m.Data)
.HasJsonConversion()
.IsRequired();
builder
.Property(m => m.Type)
.IsRequired()
.HasConversion(
t => t.AssemblyQualifiedName,
t => Type.GetType(t!)!);
builder.HasIndex(m => m.MessageId).IsUnique();
}
}
public abstract class BaseEntityConfiguration<TEntity, TId> : IEntityTypeConfiguration<TEntity>
where TEntity : Entity<TId>
where TId : struct
{
protected BaseEntityConfiguration(ILogger<BaseEntityConfiguration<TEntity, TId>> logger)
{
this.Logger = logger;
}
protected ILogger<BaseEntityConfiguration<TEntity, TId>> Logger { get; }
public virtual void Configure(EntityTypeBuilder<TEntity> builder)
{
builder
.HasKey(x => x.Id);
if (typeof(IAuditableEntity).IsAssignableFrom(builder.Metadata.ClrType))
{
Logger.LogTrace($" > Configure properties for {nameof(IAuditableEntity)}'");
builder.Property(nameof(IAuditableEntity.CreatedOn)).IsRequired().ValueGeneratedOnAdd();
builder.Property(nameof(IAuditableEntity.CreatedBy)).IsRequired().HasMaxLength(255);
builder.Property(nameof(IAuditableEntity.ModifiedOn)).IsRequired(false);
builder.Property(nameof(IAuditableEntity.ModifiedBy)).IsRequired(false).HasMaxLength(255);
}
if (typeof(ISoftDeletableEntity).IsAssignableFrom(builder.Metadata.ClrType))
{
Logger.LogTrace($" > Configure properties for {nameof(ISoftDeletableEntity)}'");
builder.Property(nameof(ISoftDeletableEntity.DeletedAt)).IsRequired(false);
builder.Property(nameof(ISoftDeletableEntity.DeletedBy)).IsRequired(false);
builder.HasQueryFilter(m => EF.Property<int?>(m, nameof(ISoftDeletableEntity.DeletedBy)) == null);
}
}
}
public class MessageLog : AuditableEntity<int>
{
public MessageLog(string messageId, object data, MessageLogType messageLogType)
{
this.MessageId = messageId;
this.Type = data.GetType();
this.Data = data;
this.MessageLogType = messageLogType;
}
private MessageLog(string messageId)
{
this.MessageId = messageId;
this.Type = default!;
this.Data = default!;
this.MessageLogType = default!;
}
public string MessageId { get; private set; }
public Type Type { get; private set; }
public MessageLogType MessageLogType { get; private set; }
public object Data { get; private set; }
}
public static class ValueConversionExtensions
{
public static PropertyBuilder<T> HasJsonConversion<T>(this PropertyBuilder<T> propertyBuilder)
where T : class, new()
{
ValueConverter<T, string> converter = new ValueConverter<T, string>
(
v => JsonConvert.SerializeObject(v),
v => JsonConvert.DeserializeObject<T>(v) ?? new T()
);
ValueComparer<T> comparer = new ValueComparer<T>
(
(l, r) => JsonConvert.SerializeObject(l) == JsonConvert.SerializeObject(r),
v => v == null ? 0 : JsonConvert.SerializeObject(v).GetHashCode(),
v => JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(v))
);
propertyBuilder.HasConversion(converter);
propertyBuilder.Metadata.SetValueConverter(converter);
propertyBuilder.Metadata.SetValueComparer(comparer);
propertyBuilder.HasColumnType("jsonb");
return propertyBuilder;
}
}
数据库上下文
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
base.OnModelCreating(builder);
}
【问题讨论】:
-
我不确定你所说的其他场景是什么意思,但不是这个特定的场景。很明显
Data属性没有被忽略,它的类型是object,所以这个异常是有意义的。您能否指出任何其他工作场景,其中您有一个object属性映射到您的数据库表中的某个列? -
问题是我不想忽略它。对象类型的 Data 属性通过 HasJsonConversion() 更改为 db 模型中的“jsonb”列。如果我直接实现 IEntityTypeConfiguration
则工作正常,但如果我继承实现此接口的基类,则由于某种原因值转换器被忽略。 Type 属性也是如此,但只有错误有点不同(类似于“您需要为此实体定义主键。”)。 -
您可以尝试调试派生配置以查看它是否实际运行?我怀疑
ApplyConfigurationsFromAssembly有问题,出于这个可能的原因,请尝试直接应用您的派生配置。 -
我刚刚尝试了一些关于应用您所说的配置的方法,在手动添加派生配置的过程中,我从 BaseEntityConfiguration 的构造函数中删除了记录器。这以某种方式解决了问题,并且它也可以与 ApplyConfigurationsFromAssembly 一起使用。我只是不确定为什么此配置只能与空构造函数一起使用,而其他没有任何对象属性(或值转换)的配置与构造函数中传递的记录器一起使用。
-
我认为
ApplyConfigurationsFromAssembly不支持IEntityTypeConfiguration的DI,因为它使用无参数构造函数创建实例。它也可能支持 DI,但在这种情况下,您可以尝试手动将派生配置类型注册为单例,以查看它是否有效(只是猜测,这种情况很棘手,因为通常我们有一个用于配置类的无参数构造函数并且它只是有效)
标签: c# .net entity-framework asp.net-core entity-framework-core