在 2 个不同的类中使用拥有类型的 EF Core 配置问题 [英] EF Core configuration problem with owned type used in 2 different classes

查看:31
本文介绍了在 2 个不同的类中使用拥有类型的 EF Core 配置问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用实体框架核心,我想在 2 个不同的类中使用相同的拥有类型.这通常没问题,但在我的情况下,我遇到了错误.

I'm using entity framework core and I would like to use the same owned type in 2 different classes. This is normally fine however in my case I am getting an error.

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

I am using a MySql database and the requirement is that all booleans are mapped to a field in the database with column type tinyint(1). To achieve this in my OnModelCreating method I loop through all the properties and if the property is boolean I map it to tinyint(1). However as soon as I use the same owned type in 2 different classes I get the error.

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

Below I have written a demo program which shows my problem. All you need to recreate this is 2 tables, organisations and contacts. Both with fields id, street and home. To use MySQL I have installed the nuget package MySql.Data.EntityFrameworkCore (v8.0.17). I've run the code in a .net core 2.2 console app.

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,因为同名的弱实体类型已经存在'.抛出错误的代码部分是这个位

After running the above code the error message that shows up is System.InvalidOperationException: 'The entity type 'Address' cannot be added to the model because a weak entity type with the same name already exists'. The part of the code that throws the error is this bit

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

如何更改我的代码,以便 OnModelCreating 方法正确运行,以便将联系人记录正确保存到数据库中?

How can I change my code so that the OnModelCreating method runs without error so that the contact record is saved correctly to the database?

推荐答案

更新(EF Core 3.x):

仍然没有公开获取EntityTypeBuilder的方法,但至少构造函数参数已经修改为IMutableEntityType类型,所以只有

Still no public way to get EntityTypeBuilder, but at least the constructor argument has been modified to be IMutableEntityType type, so only

using Microsoft.EntityFrameworkCore.Metadata.Builders;

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

is needed, and the corresponding code now is

var entityTypeBuilder = new EntityTypeBuilder(entityType);

原始(EF Core 2.x):

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

The problem is that the ClrType is not enough to identify the owned entity type, hence modelBuilder.Entity(Type) cannot be used to obtain the EntityTypeBuilder instance needed for fluently configuring the entity properties.

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

Seems like there is no good public way to do that in EF Core 2.x, so all I can suggest is to use some of the EF Core internals (luckily publicly accessible under the typical internal usage warning).

您需要以下 usings:

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

第一个用于 EntityTypeBuilder 类,第二个用于 AsEntityType() 扩展方法,它使您可以访问实现 IEntityType,尤其是 Builder 属性.

The first is for EntityTypeBuilder class, the second is for AsEntityType() extension method which gives you access to the internal class implementing the IEntityType, and in particular the Builder property.

修改后的代码如下:

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)");
        }
    }
}

这篇关于在 2 个不同的类中使用拥有类型的 EF Core 配置问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆