“无法映射‘List<NNN>’属性‘PPP’...”尝试使用实体框架中的关系播种模型时出现错误

问题描述 投票:0回答:1

实体:

using CommonSolution.Entities.Task;
using Microsoft.EntityFrameworkCore;

namespace EntityFramework.Models;

[System.ComponentModel.DataAnnotations.Schema.Table("tasks_custom_folders")]
[Microsoft.EntityFrameworkCore.EntityTypeConfiguration(typeof(TaskCustomFolderModel.Configuration))]
public class TaskCustomFolderModel
{

  /* [ Theory ] `Guid.NewGuid()` return the string of 36 characters. See https://stackoverflow.com/a/4458925/4818123 */
  [System.ComponentModel.DataAnnotations.Key]
  [System.ComponentModel.DataAnnotations.MaxLength(36)]
  public string ID { get; set; } = Guid.NewGuid().ToString();

  
  /* ━━━ Title ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
  [System.ComponentModel.DataAnnotations.MaxLength(TaskCustomFolder.Title.MAXIMAL_CHARACTERS_COUNT)]
  public string Title { get; set; } = null!;
  
  
  /* ━━━ Relatives ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
  /* ─── Parent ───────────────────────────────────────────────────────────────────────────────────────────────────── */
  /* [ Theory ] Although generally the explicit specifying of navigation property is optional, it is required for seeding. */
  /* [ Theory ] `Guid.NewGuid()` return the string of 36 characters. See https://stackoverflow.com/a/4458925/4818123 */
  [System.ComponentModel.DataAnnotations.MaxLength(36)]
  public string? ParentID { get; set; }
  
  public TaskCustomFolderModel? Parent { get; set; }
  
  
  /* ─── Children ─────────────────────────────────────────────────────────────────────────────────────────────────── */
  public List<TaskCustomFolderModel> Children { get; set; } = [];
  
  
  /* ━━━ Order Among Siblings ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
  public uint OrderAmongSiblings { get; set; }
  
  
  /* ━━━ Configuration ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
  public class Configuration : Microsoft.EntityFrameworkCore.IEntityTypeConfiguration<TaskCustomFolderModel>
  {

    private const string TABLE_NAME = "TasksCustomFolders";

    public void Configure(
      Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder<TaskCustomFolderModel> builder)
    {

      builder.ToTable(Configuration.TABLE_NAME);

      builder.
          Property(taskCustomFolderModel => taskCustomFolderModel.Title).
          IsRequired(TaskCustomFolder.Title.IS_REQUIRED);

      builder.
          Property(taskCustomFolderModel => taskCustomFolderModel.Parent).
          IsRequired(TaskCustomFolder.Parent.IS_REQUIRED);
      
      builder.
          Property(taskCustomFolderModel => taskCustomFolderModel.ParentID).
          IsRequired(TaskCustomFolder.Parent.IS_REQUIRED);
      
      builder.
          Property(taskCustomFolderModel => taskCustomFolderModel.Children).
          IsRequired(TaskCustomFolder.Children.IS_REQUIRED);
      
      builder.
          Property(taskCustomFolderModel => taskCustomFolderModel.OrderAmongSiblings).
          IsRequired(TaskCustomFolder.OrderAmongSiblings.IS_REQUIRED);
    }
  }
}

当我尝试通过调用

base.Database.EnsureCreated();
为数据库播种时出错:

System.InvalidOperationException:''列表' 无法映射属性“TaskCustomFolderModel.Children”,因为数据库提供程序不支持此类型。考虑使用值转换器将属性值转换为数据库支持的类型。有关更多信息,请参阅 https://aka.ms/efcore-docs-value-converters。或者,使用“[NotMapped]”属性或使用“OnModelCreating”中的“EntityTypeBuilder.Ignore”从模型中排除该属性。'

根据错误,数据库提供程序不支持某些内容,但是我用作参考并且也有关系的下面的示例工作正常!

public class MenuItem
{
    public int Id { get; set; }
    public string? Title { get; set; }
    public int? ParentId { get; set; }
    public MenuItem? Parent { get; set; }
    public List<MenuItem> Children { get; set; } = new();
}

using Microsoft.EntityFrameworkCore;
 
public class ApplicationContext : DbContext
{
    public DbSet<MenuItem> MenuItems { get; set; } = null!;
 
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite("Data Source=helloapp.db");
    }
}

using (ApplicationContext db = new ApplicationContext())
{
    db.Database.EnsureDeleted();
    db.Database.EnsureCreated();
 
    MenuItem file = new MenuItem { Title = "File" };
    MenuItem edit = new MenuItem { Title = "Edit" };
    MenuItem open = new MenuItem { Title = "Open", Parent = file };
    MenuItem save = new MenuItem { Title = "Save", Parent = file };
 
    MenuItem copy = new MenuItem { Title = "Copy", Parent = edit };
    MenuItem paste = new MenuItem { Title = "Paste", Parent = edit };
 
    db.MenuItems.AddRange(file, edit, open, save, copy, paste);
    db.SaveChanges();
}

using (ApplicationContext db = new ApplicationContext())
{
    var menuItems = db.MenuItems.ToList();
    Console.WriteLine("All Menu:");

    foreach (MenuItem m in menuItems)
    {
        Console.WriteLine(m.Title);
    }

    Console.WriteLine();
    
    var fileMenu = db.MenuItems.FirstOrDefault(m => m.Title == "File");

    if (fileMenu != null)
    {
        Console.WriteLine(fileMenu.Title);

        foreach (var m in fileMenu.Children)
        {
            Console.WriteLine($"---{m.Title}");
        }
    }
}

所以看起来问题出在其他地方,所以现在我不会说我正在使用哪个数据库提供商。

带有种子代码的数据库上下文类:

public class RemoteDatabaseContext : DatabaseContext
{
  public RemoteDatabaseContext()
  {
    // base.Database.EnsureDeleted();
    base.Database.EnsureCreated();
  }
  
  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  {
    base.OnConfiguring(optionsBuilder);

    optionsBuilder.UseNpgsql("Host=localhost;Port=5432;Username=postgres;Password=pass1234");
  }

  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    modelBuilder.
        Entity<TaskCustomFolderModel>().
        HasData(TaskCustomFolderModel.FromBusinessRulesEntities(SampleTasksCustomFoldersRepository.TaskCustomFolders));
  }
}

public abstract class DatabaseContext : Microsoft.EntityFrameworkCore.DbContext
{
  public DbSet<TaskModel> TasksModels { get; internal set; } = null!;
  public DbSet<TaskCustomFolderModel> TaskCustomFoldersModels { get; internal set; } = null!;
  public DbSet<LocationModel> LocationModels { get; internal set; } = null!;
  
  public DbSet<PersonModel> PeopleModels { get; internal set; } = null!;
}

为什么您通过 Fluent API 设置

IsRequired
,而通过属性设置最大字符数?

简短的回答:由于干净的架构,并且因为

TaskCustomFolderModel
和其他实体框架模型必须由于业务规则的更改而更改,而无需手动代码编辑。

长答案:

根据清洁架构,业务规则不得依赖于任何框架,而

TaskCustomFolderModel
则依赖于任何框架。因此,除了
TaskCustomFolderModel
类之外,还有来自业务规则的类:

public class TaskCustomFolder
{
  public required string ID { get; init; }
  
  /* ━━━ Title ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
  public required string title { get; set; }
  
  public abstract record Title
  {
    public const bool IS_REQUIRED = true;
    public const byte MINIMAL_CHARACTERS_COUNT = 1;
    public const byte MAXIMAL_CHARACTERS_COUNT = Byte.MaxValue;
  }
  
  /* ━━━ Relatives ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
  /* ─── Parent ───────────────────────────────────────────────────────────────────────────────────────────────────── */
  public TaskCustomFolder? parent { get; set; }
  
  public abstract record Parent
  {
    public const bool IS_REQUIRED = false;
  }
  
  /* ─── Children ─────────────────────────────────────────────────────────────────────────────────────────────────── */
  public List<TaskCustomFolder> children { get; set; } = [];
  
  public abstract record Children
  {
    public const bool IS_REQUIRED = false;
  }
  
  /* ━━━ Order Among Siblings ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
  public required uint orderAmongSiblings { get; set; }
  
  public abstract record OrderAmongSiblings
  {
    public const bool IS_REQUIRED = true;
    public const uint MINIMAL_VALUE = 1;
    public const uint MAXIMAL_VALUE = UInt32.MaxValue;
  }
  
  /* ━━━ Path ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
  public string path
  {
    get
    {
      TaskCustomFolder currentDepthLevel = this;
      List<string> pathSegments = [ currentDepthLevel.title ];

      while (currentDepthLevel.parent is not null)
      {
        currentDepthLevel = currentDepthLevel.parent;
        pathSegments.Add(currentDepthLevel.title);
      }

      return String.Join("/", pathSegments);
    }
  }
}

如果我们要更改某些属性的

MAXIMAL_CHARACTERS_COUNT
,则无需编辑
TaskCustomFolderModel
,因为它通过属性引用业务规则:

public class TaskCustomFolderModel
{
  // ...   [System.ComponentModel.DataAnnotations.MaxLength(TaskCustomFolder.Title.MAXIMAL_CHARACTERS_COUNT)]
  public string Title { get; set; } = null!;
}

但是,没有像

Required
这样接受参数的
TaskCustomFolder.Children.IS_REQUIRED
属性。因此只能通过流畅的 API(或创建新属性)来完成。

c# entity-framework
1个回答
0
投票

实际上很简单,但是异常消息可能会更有帮助。

异常原因是

builder.
   Property(taskCustomFolderModel => taskCustomFolderModel.Children).
   IsRequired(TaskCustomFolder.Children.IS_REQUIRED);

这使得 EF 将

Children
视为另一种类型的引用属性,就像
Parent
一样。但是,
List<TaskCustomFolderModel>
不是映射类型。尽管这很明显,但异常消息可能会指出
Required
不能应用于集合属性。

如果您从集合属性中删除

IsRequired
调用,配置将有效。

您可能会反对该属性应该是必需的,但从数据建模的角度来看,这个要求没有意义。无法在基于 SQL 的数据库中强制执行 1:1..n 关系,因此 EF 不提供配置语句来支持它。使集合不为空只是为了应用程序方便。至于存储的数据,空集合和

null
集合之间没有区别。

© www.soinside.com 2019 - 2024. All rights reserved.