在 2 个不同 类 中使用的拥有类型的 EF Core 配置问题
EF Core configuration problem with owned type used in 2 different classes
我正在使用 entity framework 核心,我想在 2 个不同的 类 中使用相同的拥有类型。这通常没问题,但在我的情况下出现错误。
我正在使用 MySql 数据库,要求所有布尔值都映射到数据库中列类型为 tinyint(1) 的字段。为了在我的 OnModelCreating 方法中实现这一点,我循环遍历所有属性,如果 属性 是布尔值,我将它映射到 tinyint(1)。但是,一旦我在 2 个不同的 类 中使用相同的拥有类型,我就会收到错误消息。
下面我写了一个演示程序来说明我的问题。您只需要重新创建 2 个表、组织和联系人。两者都有字段 ID、街道和家庭。要使用 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:'实体类型 'Address' 无法添加到模型中,因为具有同名已存在'。代码中抛错的部分就是这个bit
if (property.PropertyInfo.PropertyType.IsBoolean())
{
modelBuilder.Entity(entityType.ClrType)
.Property(property.Name)
.HasConversion(new BoolToZeroOneConverter<short>())
.HasColumnType("tinyint(1)");
}
如何更改我的代码以使 OnModelCreating 方法 运行 没有错误,以便将联系人记录正确保存到数据库中?
更新(EF Core 3.x):
仍然没有public的方式得到EntityTypeBuilder
,但至少构造函数参数已经被修改为IMutableEntityType
类型,所以只有
using Microsoft.EntityFrameworkCore.Metadata.Builders;
需要,现在对应的代码是
var entityTypeBuilder = new EntityTypeBuilder(entityType);
原始(EF Core 2.x):
问题是 ClrType
不足以识别拥有的实体类型,因此 modelBuilder.Entity(Type)
无法获得流畅配置实体属性所需的 EntityTypeBuilder
实例.
似乎没有好的 public 方法可以在 EF Core 2.x 中做到这一点,所以我只能建议使用一些EF Core 内部(幸运的是public在典型的内部使用警告下可以访问)。
您需要以下 using
:
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
第一个用于 EntityTypeBuilder
class,第二个用于 AsEntityType()
扩展方法,它使您可以访问实现 IEntityType
的内部 class ,特别是 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)");
}
}
}
我正在使用 entity framework 核心,我想在 2 个不同的 类 中使用相同的拥有类型。这通常没问题,但在我的情况下出现错误。
我正在使用 MySql 数据库,要求所有布尔值都映射到数据库中列类型为 tinyint(1) 的字段。为了在我的 OnModelCreating 方法中实现这一点,我循环遍历所有属性,如果 属性 是布尔值,我将它映射到 tinyint(1)。但是,一旦我在 2 个不同的 类 中使用相同的拥有类型,我就会收到错误消息。
下面我写了一个演示程序来说明我的问题。您只需要重新创建 2 个表、组织和联系人。两者都有字段 ID、街道和家庭。要使用 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:'实体类型 'Address' 无法添加到模型中,因为具有同名已存在'。代码中抛错的部分就是这个bit
if (property.PropertyInfo.PropertyType.IsBoolean())
{
modelBuilder.Entity(entityType.ClrType)
.Property(property.Name)
.HasConversion(new BoolToZeroOneConverter<short>())
.HasColumnType("tinyint(1)");
}
如何更改我的代码以使 OnModelCreating 方法 运行 没有错误,以便将联系人记录正确保存到数据库中?
更新(EF Core 3.x):
仍然没有public的方式得到EntityTypeBuilder
,但至少构造函数参数已经被修改为IMutableEntityType
类型,所以只有
using Microsoft.EntityFrameworkCore.Metadata.Builders;
需要,现在对应的代码是
var entityTypeBuilder = new EntityTypeBuilder(entityType);
原始(EF Core 2.x):
问题是 ClrType
不足以识别拥有的实体类型,因此 modelBuilder.Entity(Type)
无法获得流畅配置实体属性所需的 EntityTypeBuilder
实例.
似乎没有好的 public 方法可以在 EF Core 2.x 中做到这一点,所以我只能建议使用一些EF Core 内部(幸运的是public在典型的内部使用警告下可以访问)。
您需要以下 using
:
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
第一个用于 EntityTypeBuilder
class,第二个用于 AsEntityType()
扩展方法,它使您可以访问实现 IEntityType
的内部 class ,特别是 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)");
}
}
}