我有一个通用的 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);
}
最佳答案
TL;博士
尝试向您的 IEntityTypeConfiguration
添加一个空的构造函数实现。否则,如果您仍想在实体类型配置中使用 DI,可能值得查看 this issue .
我认为您的 IEntityTypeConfiguration
中没有注入(inject)的记录器将与 ApplyConfigurationsFromAssembly
一起工作.来自 the source code在该方法中,似乎在使用反射来搜索配置类时,它需要一个空的构造函数,以便它可以实例化它们。
由于您的IEntityTypeConfiguration
s 缺少默认的空构造函数,ApplyConfigurationsFromAssembly
可能没有接他们。
如果您仍想在实体类型配置中使用 DI,可能值得查看 this issue , 其中 @ajcvickers 给出了如何做的详细解释。
这是 Github 问题答案代码的副本/意大利面:
public abstract class EntityTypeConfigurationDependency
{
public abstract void Configure(ModelBuilder modelBuilder);
}
public abstract class EntityTypeConfigurationDependency<TEntity>
: EntityTypeConfigurationDependency, IEntityTypeConfiguration<TEntity>
where TEntity : class
{
public abstract void Configure(EntityTypeBuilder<TEntity> builder);
public override void Configure(ModelBuilder modelBuilder)
=> Configure(modelBuilder.Entity<TEntity>());
}
public class Blog
{
public int Pk { get; set; }
public ICollection<Post> Posts { get; set; }
}
public class BlogConfiguration : EntityTypeConfigurationDependency<Blog>
{
public override void Configure(EntityTypeBuilder<Blog> builder)
{
builder.HasKey(e => e.Pk);
}
}
public class Post
{
public int Pk { get; set; }
public Blog Blog { get; set; }
}
public class PostConfiguration : EntityTypeConfigurationDependency<Post>
{
public override void Configure(EntityTypeBuilder<Post> builder)
{
builder.HasKey(e => e.Pk);
}
}
public class Program
{
private static ILoggerFactory ContextLoggerFactory
=> LoggerFactory.Create(b => b.AddConsole().SetMinimumLevel(LogLevel.Information));
public static void Main()
{
var services = new ServiceCollection()
.AddDbContext<SomeDbContext>(
b => b.UseSqlServer(Your.ConnectionString)
.EnableSensitiveDataLogging()
.UseLoggerFactory(ContextLoggerFactory));
foreach (var type in typeof(SomeDbContext).Assembly.DefinedTypes
.Where(t => !t.IsAbstract
&& !t.IsGenericTypeDefinition
&& typeof(EntityTypeConfigurationDependency).IsAssignableFrom(t)))
{
services.AddSingleton(typeof(EntityTypeConfigurationDependency), type);
}
var serviceProvider = services.BuildServiceProvider();
using (var scope = serviceProvider.CreateScope())
{
var context = scope.ServiceProvider.GetService<SomeDbContext>();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
}
}
}
public class SomeDbContext : DbContext
{
private readonly IEnumerable<EntityTypeConfigurationDependency> _configurations;
public SomeDbContext(
DbContextOptions<SomeDbContext> options,
IEnumerable<EntityTypeConfigurationDependency> configurations)
: base(options)
{
_configurations = configurations;
}
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entityTypeConfiguration in _configurations)
{
entityTypeConfiguration.Configure(modelBuilder);
}
}
}
关于c# - EF Core 属性值转换在派生实体配置中不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66051215/