c# - EF Core 属性值转换在派生实体配置中不起作用

标签 c# .net entity-framework asp.net-core entity-framework-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();

        }
    }
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在该方法中,似乎在使用反射来搜索配置类时,它需要一个空的构造函数,以便它可以实例化它们。
EF core source code for ApplyConfigurationsFromAssembly
由于您的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/

相关文章:

c# - 使用 Entity Framework 的通用存储库,如何查询实现特定接口(interface)的实体?

c# - 使用 FileReader.readAsDataUrl 上传图片到 Web Api 服务

c# - 为什么在创建新对象时 C# 链表中的值会发生变化?

.net - 在 .NET 中使用 Thrift 在 Hbase 上进行 MapReduce?

.net - 版本控制 .NET 构建

c# - ef4 导致 Web 服务中的循环引用

c# - GetShortPathNameW 找不到指定的文件

c# - C# 方法是否占用内存?

c# - 使用 JsonConvert.DeserializeObject 反序列化派生对象列表

c# - Entity Framework 5 无效列名错误