【问题标题】:EF Core configuration problem with owned type used in 2 different classes2 个不同类中使用的自有类型的 EF Core 配置问题
【发布时间】:2019-08-28 12:24:52
【问题描述】:

我正在使用实体框架核心,我想在 2 个不同的类中使用相同的自有类型。这通常很好,但在我的情况下,我遇到了错误。

我正在使用 MySql 数据库,要求所有布尔值都映射到数据库中列类型为 tinyint(1) 的字段。为了在我的 OnModelCreating 方法中实现这一点,我遍历所有属性,如果属性是布尔值,我将其映射到 tinyint(1)。但是,一旦我在 2 个不同的类中使用相同的自有类型,就会出现错误。

下面我写了一个演示程序来显示我的问题。您只需要重新创建 2 个表格、组织和联系人即可。都有字段 id、street 和 home。要使用 MySQL,我已经安装了 nuget 包 MySql.Data.EntityFrameworkCore (v8.0.17)。我已经在 .net core 2.2 控制台应用程序中运行了代码。

using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

namespace MyDemo
{
    class Program
    {
        static void Main(string[] args)
        {
           using(var ctx = new MyDbContext())
            {
                var contact = new Contact
                {                
                    Address = new Address
                    {
                        Street = "x",
                        Home = true
                    }
                };
                ctx.Contacts.Add(contact);
                ctx.SaveChanges();
            }
        }
    }


    public class MyDbContext: DbContext
    {
        public MyDbContext()        
        {

        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseMySQL("{my connection string}");                
            base.OnConfiguring(optionsBuilder);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Contact>()
                .OwnsOne(p => p.Address,
                a =>
                {
                    a.Property(p => p.Street)
                    .HasColumnName("street")
                    .HasDefaultValue("");
                    a.Property(p => p.Home)
                    .HasColumnName("home")
                    .HasDefaultValue(false);
                });

            modelBuilder.Entity<Organisation>()
                .OwnsOne(p => p.Address,
                a =>
                {
                    a.Property(p => p.Street)
                    .HasColumnName("street")
                    .HasDefaultValue("");
                    a.Property(p => p.Home)
                    .HasColumnName("home")
                    .HasDefaultValue(false);
                });

            var entityTypes = modelBuilder.Model.GetEntityTypes()          
            .ToList();

            foreach (var entityType in entityTypes)
            {
                var properties = entityType
                    .GetProperties()
                    .ToList();


                foreach (var property in properties)
                {
                    if (property.PropertyInfo == null)
                    {
                        continue;
                    }

                    if (property.PropertyInfo.PropertyType.IsBoolean())
                    {
                        modelBuilder.Entity(entityType.ClrType)
                        .Property(property.Name)
                        .HasConversion(new BoolToZeroOneConverter<short>())
                        .HasColumnType("tinyint(1)");
                    }
                }
            }

            base.OnModelCreating(modelBuilder);
        }

        public DbSet<Contact>Contacts { get; set; }
        public DbSet<Organisation>Organisations { get; set; }
    }

    public class Contact
    {
        public int Id { get; set; }
        public Address Address { get; set; }

        //other contact fields
    }

    public class Organisation
    {
        public int Id { get; set; }
        public Address Address { get; set; }

        //other organisation fields
    }

    public class Address
    {
        public string Street { get; set; }
        public bool Home{ get; set; }
    }

    public static class TypeExtensions
    {
        public static bool IsBoolean(this Type type)
        {
            Type t = Nullable.GetUnderlyingType(type) ?? type;
            return t == typeof(bool);
        }
    }
}

运行上述代码后,显示的错误消息是 System.InvalidOperationException: 'The entity type 'Address' cannot be added to the model because aweak entity type has already exists'.抛出错误的部分代码就是这个位

if (property.PropertyInfo.PropertyType.IsBoolean())
{
     modelBuilder.Entity(entityType.ClrType)
    .Property(property.Name)
    .HasConversion(new BoolToZeroOneConverter<short>())
    .HasColumnType("tinyint(1)");
}

如何更改我的代码以使 OnModelCreating 方法运行时不会出错,从而将联系人记录正确保存到数据库中?

【问题讨论】:

    标签: c# mysql ef-core-2.2


    【解决方案1】:

    更新(EF Core 3.x):

    目前还没有公开方式获取EntityTypeBuilder,但至少构造函数参数已经修改为IMutableEntityType类型,所以只有

    using Microsoft.EntityFrameworkCore.Metadata.Builders;
    

    是需要的,现在对应的代码是

    var entityTypeBuilder = new EntityTypeBuilder(entityType);
    

    原始(EF Core 2.x):

    问题是ClrType不足以识别拥有的实体类型,因此modelBuilder.Entity(Type)不能用于获取流畅配置实体属性所需的EntityTypeBuilder实例。

    似乎在 EF Core 2.x 中没有好的 public 方法可以做到这一点,所以我只能建议使用一些 EF Core internals (幸运的是,在典型的内部使用警告下可公开访问)。

    您需要以下usings:

    using Microsoft.EntityFrameworkCore.Metadata.Builders;
    using Microsoft.EntityFrameworkCore.Metadata.Internal;
    

    第一个用于EntityTypeBuilder 类,第二个用于AsEntityType() 扩展方法,它允许您访问实现IEntityType 的内部类,尤其是Builder 属性。

    修改后的代码如下:

    var entityTypes = modelBuilder.Model.GetEntityTypes()
        .ToList();
    
    foreach (var entityType in entityTypes)
    {
        var properties = entityType
            .GetProperties()
            .ToList();
    
        // (1)
        var entityTypeBuilder = new EntityTypeBuilder(entityType.AsEntityType().Builder);
    
        foreach (var property in properties)
        {
            if (property.PropertyInfo == null)
            {
                continue;
            }
    
            if (property.PropertyInfo.PropertyType.IsBoolean())
            {
                entityTypeBuilder // (2)
                .Property(property.Name)
                .HasConversion(new BoolToZeroOneConverter<short>())
                .HasColumnType("tinyint(1)");
            }
        }
    }
    

    【讨论】:

    • 刚刚更新了我的代码,一切正常,没有错误。谢谢!
    • @DaveBarnett Public - 仍然没有。查看更新。其实一切都是公开的,只是构造函数被标记为[EntityFrameworkInternal]
    猜你喜欢
    • 2018-06-04
    • 1970-01-01
    • 1970-01-01
    • 2021-10-22
    • 2021-08-22
    • 2022-01-25
    • 2019-02-11
    • 2020-11-17
    • 2023-03-31
    相关资源
    最近更新 更多