C# EF核心属性值转换在派生实体配置中不起作用

C# EF核心属性值转换在派生实体配置中不起作用,c#,.net,entity-framework,asp.net-core,entity-framework-core,C#,.net,Entity Framework,Asp.net Core,Entity Framework Core,我有一个通用的EF BaseEntityConfiguration类,用于设置基本属性(如主键、用于软删除的属性、查询筛选器等),以及存储System.Type和JSON属性的实体的派生配置。如果我不使用泛型类,只实现了IEntityTypeConfiguration,那么值转换就可以工作,并且没有错误。然而,如果我从基类继承,我会遇到关于在不进行任何转换的情况下保存类型和对象的核心问题。其他继承自基类且不需要转换的配置工作正常 错误: 错误:无法映射属性“MessageLog.Data”,因为

我有一个通用的EF BaseEntityConfiguration类,用于设置基本属性(如主键、用于软删除的属性、查询筛选器等),以及存储System.Type和JSON属性的实体的派生配置。如果我不使用泛型类,只实现了IEntityTypeConfiguration,那么值转换就可以工作,并且没有错误。然而,如果我从基类继承,我会遇到关于在不进行任何转换的情况下保存类型和对象的核心问题。其他继承自基类且不需要转换的配置工作正常

错误:

错误:无法映射属性“MessageLog.Data”,因为它的类型为“object”,它不是受支持的基元类型或有效的实体类型。显式映射此属性,或使用“[NotMapped]”属性或使用“OnModelCreating”中的“EntityTypeBuilder.ignore”忽略它。

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();

        }
    }
TL;博士 尝试将空构造函数添加到
IEntityTypeConfiguration
实现中。否则,如果您仍然希望在实体类型配置中使用DI,那么可能值得一看


我认为
IEntityTypeConfiguration
中注入的记录器不会与
ApplyConfigurationsFromAssembly
一起工作。从该方法的角度来看,在使用反射搜索配置类时,似乎需要一个空构造函数,以便能够实例化它们

由于您的
IEntityTypeConfiguration
s缺少默认的空构造函数,因此
ApplyConfigurationsFromAssembly
可能无法获取它们

如果您仍然希望在实体类型配置中使用DI,那么可能值得一看,@ajcvickers详细解释了如何使用DI

这是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);
        }
    }
}
公共抽象类EntityTypeConfigurationDependency
{
公共抽象空配置(ModelBuilder ModelBuilder);
}
公共抽象类EntityTypeConfigurationDependency
:EntityTypeConfigurationDependency,IEntityTypeConfiguration
地点:班级
{
公共抽象空配置(EntityTypeBuilder);
公共覆盖无效配置(ModelBuilder ModelBuilder)
=>配置(modelBuilder.Entity());
}
公共类博客
{
公共int Pk{get;set;}
公共ICollection Posts{get;set;}
}
公共类BlogConfiguration:EntityTypeConfigurationDependency
{
公共覆盖无效配置(EntityTypeBuilder)
{
builder.HasKey(e=>e.Pk);
}
}
公营职位
{
公共int Pk{get;set;}
公共博客Blog{get;set;}
}
公共类后配置:EntityTypeConfigurationDependency
{
公共覆盖无效配置(EntityTypeBuilder)
{
builder.HasKey(e=>e.Pk);
}
}
公共课程
{
私有静态iLogger工厂ContextLoggerFactory
=>LoggerFactory.Create(b=>b.AddConsole().SetMinimumLevel(LogLevel.Information));
公共静态void Main()
{
var services=newservicecolection()
.AddDbContext(
b=>b.UseSqlServer(您的.ConnectionString)
.EnableSensitiveDataLogging()
.UseLoggerFactory(ContextLoggerFactory));
foreach(typeof(SomeDbContext)中的变量类型).Assembly.DefinedTypes
.其中(t=>!t.IsAbstract
&&!t.IsGenericTypeDefinition
&&typeof(EntityTypeConfigurationDependency).IsAssignableFrom(t)))
{
AddSingleton(typeof(EntityTypeConfigurationDependency),type);
}
var serviceProvider=services.BuildServiceProvider();
使用(var scope=serviceProvider.CreateScope())
{
var context=scope.ServiceProvider.GetService();
context.Database.EnsureDeleted();
context.Database.recreated();
}
}
}
公共类SomeDbContext:DbContext
{
私有只读IEnumerable\u配置;
公共SomeDbContext(
DbContextOptions选项,
IEnumerable(可数配置)
:基本(选项)
{
_配置=配置;
}
公共数据库集博客{get;set;}
公共DbSet Posts{get;set;}
模型创建时受保护的覆盖无效(ModelBuilder ModelBuilder)
{
foreach(在_配置中的var entityTypeConfiguration)
{
entityTypeConfiguration.Configure(modelBuilder);
}
}
}

我不确定您所说的其他工作场景是什么意思,但不确定这一特定场景是什么意思。显然,
数据
属性没有被忽略,它的类型是
对象
,因此异常是有意义的。您是否可以指出任何其他工作场景,其中您有一个映射到db表中某个列的
object
属性?问题是我不想忽略它。类型为object的数据属性通过HasJsonConversion()更改为db模型中的“jsonb”列。如果我直接实现了IEntityTypeConfiguration,那么它可以正常工作,但是如果我继承了实现这个接口的基类,那么由于某种原因,值转换器就会被忽略。Type属性也是一样,但只是错误有点不同(类似于“您需要为此实体定义一个主键。”)。能否尝试调试派生配置以查看它是否实际运行?我怀疑
ApplyConfigurationsFromAssembly
有什么问题,出于可能的原因,请尝试直接应用派生配置。我刚刚尝试了两件与应用配置有关的事情,如您所说,在手动添加派生配置的过程中,我从BaseE中删除了记录器
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);
        }
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);
        }
    }
}