Код: Выделить всё
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 Children { get; set; } = [];
/* ━━━ Order Among Siblings ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
public uint OrderAmongSiblings { get; set; }
/* ━━━ Configuration ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
public class Configuration : Microsoft.EntityFrameworkCore.IEntityTypeConfiguration
{
private const string TABLE_NAME = "TasksCustomFolders";
public void Configure(
Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder 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);
}
}
}
System.InvalidOperationException: свойство List
TaskCustomFolderModel.Children не удалось сопоставить, поскольку поставщик базы данных не поддерживает этот тип. Рассмотрите возможность преобразования значения свойства в тип, поддерживаемый базой данных, с помощью преобразователя значений. См. https://aka.ms/efcore-docs-value-converters для получения дополнительной информации. Альтернативно исключите свойство из модели, используя атрибут «[NotMapped]» или используя «EntityTypeBuilder.Ignore» в «OnModelCreating».
Согласно ошибка: поставщик базы данных что-то не поддерживает, однако приведенный ниже пример, который я использовал для справки и который также имеет отношения, работает нормально!
Код: Выделить всё
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 Children { get; set; } = new();
}
using Microsoft.EntityFrameworkCore;
public class ApplicationContext : DbContext
{
public DbSet 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().
HasData(TaskCustomFolderModel.FromBusinessRulesEntities(SampleTasksCustomFoldersRepository.TaskCustomFolders));
}
}
public abstract class DatabaseContext : Microsoft.EntityFrameworkCore.DbContext
{
public DbSet TasksModels { get; internal set; } = null!;
public DbSet TaskCustomFoldersModels { get; internal set; } = null!;
public DbSet LocationModels { get; internal set; } = null!;
public DbSet PeopleModels { get; internal set; } = null!;
}
Краткий ответ: из-за чистой архитектуры и потому что TaskCustomFolderModel и другие модели Entity Framework должны измениться из-за изменений в бизнес-правилах без ручного редактирования кода, если это возможно.
Развернутый ответ:
Согласно чистой архитектуре, бизнес-правила НЕ должны зависеть от каких-либо платформ, в то время как 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 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 pathSegments = [ currentDepthLevel.title ];
while (currentDepthLevel.parent is not null)
{
currentDepthLevel = currentDepthLevel.parent;
pathSegments.Add(currentDepthLevel.title);
}
return String.Join("/", pathSegments);
}
}
}
Код: Выделить всё
public class TaskCustomFolderModel
{
// ... [System.ComponentModel.DataAnnotations.MaxLength(TaskCustomFolder.Title.MAXIMAL_CHARACTERS_COUNT)]
public string Title { get; set; } = null!;
}
Подробнее здесь: https://stackoverflow.com/questions/791 ... ng-to-seed