Ves*_*rov 5 .net c# entity-framework entity-framework-core asp.net-core
我有一个通用 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();
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
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; }
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)
数据库上下文
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
base.OnModelCreating(builder);
}
Run Code Online (Sandbox Code Playgroud)
尝试在您的实现中添加一个空的构造函数IEntityTypeConfiguration
。否则,如果您仍然想在实体类型配置中使用 DI,则可能值得查看此问题。
我不认为你的注入记录器IEntityTypeConfiguration
会与ApplyConfigurationsFromAssembly
. 从该方法的源代码来看,似乎在使用反射搜索配置类时,它需要一个空构造函数,以便可以实例化它们。
由于您的IEntityTypeConfiguration
s 缺少默认的空构造函数,因此ApplyConfigurationsFromAssembly
可能不会选择它们。
如果您仍然想在实体类型配置中使用 DI,则可能值得查看此问题,其中@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);
}
}
}
Run Code Online (Sandbox Code Playgroud)