我遇到这样一种情况,一个实体有一个不活动的子项列表。此外,他们还有另一个相同类型的“当前/事件”子实体。
以下是理想的建模,但我不知道如何使用 Entity Framework Core:
public class Customer
{
public Application CurrentApplication { get; set; }
public List<Application> PastApplications { get; set; }
}
public class Application
{
public bool IsCurrent { get; set; }
public Customer Customer { get; set; }
}
过去,我通常将其建模为:
public class Customer
{
public Application CurrentApplication => AllApplications.Single(a => a.IsCurrent);
public List<Application> PastApplications => AllApplications.Where(a => !a.IsCurrent);
public List<Application> AllApplications { get; set; }
}
public class Application
{
public bool IsCurrent { get; set; }
public Customer Customer { get; set; }
}
但是,我觉得这可能会导致另一个Application被错误地设置为IsCurrent,从而破坏了.Single().
从 DDD 的角度来看,建议的实现方法是什么?如果这与 EF Core 的功能不相符,什么是好的实用建议?
请您参考如下方法:
我不认为这是 DDD 问题,而是如何设计关系数据库模型以及如何使用 EF Core 的问题。
首先您需要确定客户和应用程序之间的关系:
- 一对多,即一个客户可能有零个或多个应用程序,但一个应用程序只属于一个客户。这种场景也称为主从关系。 Many 端的实体(在本例中为应用程序)存储对其所有者(客户)的引用(称为外键)。
- 多对多,即一个客户可能有零个或多个应用程序,但一个应用程序也可能属于零个或多个客户。此场景使用额外的“连接”表(通常命名为 CustomerApplication)建模,因此问题最终解决为两个 One-to-May 关系。
如果在给定时间(每个客户)只有一个事件应用程序,则可以使用客户和应用程序之间的一对一(准确地说是零对一)关系对事件应用程序建模(与客户方的外键)。它也可以在您尝试时使用 Application 上的标志字段进行建模,但这不像外键那样防错(但可能具有更好的性能)。
您发布的代码更像是一个一对多的场景,因此我展示了该案例的示例。了解以下内容,您可以根据需要轻松地将其更改为多对多。
首先让我们定义实体:
public class Customer
{
public int Id { get; set; }
public int? CurrentApplicationId { get; set; }
public Application CurrentApplication { get; set; }
public ICollection<Application> Applications { get; set; }
}
public class Application
{
public int Id { get; set; }
public Customer Customer { get; set; }
}
唯一有趣的部分是 int?当前应用程序 ID。我们需要为我们的零对多关系明确定义外键(稍后会详细介绍)。可为空的 int (int?) 告诉 EF 该字段可以为 NULL。
EF 通常能够找出关系映射,但在这种情况下,我们需要明确地向它解释它们:
class DataContext : DbContext
{
// ctor omitted for brevity
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>(customer =>
{
customer.HasMany(entity => entity.Applications)
.WithOne(relatedEntity => relatedEntity.Customer)
.OnDelete(DeleteBehavior.Cascade);
customer.HasOne(entity => entity.CurrentApplication)
.WithOne()
.HasForeignKey<Customer>(entity => entity.CurrentApplicationId);
});
}
public DbSet<Application> Applications { get; set; }
public DbSet<Customer> Customers { get; set; }
}
OnModelCreating 方法中发生的事情称为 fluent API configuration .本主题与conventions必须理解和控制 EF 如何将实体映射到数据库表。
基于映射,EF 能够生成(参见 code-first migrations)以下数据库架构:
CREATE TABLE [Customers] (
[Id] INTEGER NOT NULL
, [CurrentApplicationId] bigint NULL
, CONSTRAINT [sqlite_master_PK_Customers] PRIMARY KEY ([Id])
, FOREIGN KEY ([CurrentApplicationId]) REFERENCES [Applications] ([Id]) ON DELETE RESTRICT ON UPDATE NO ACTION
);
CREATE UNIQUE INDEX [IX_Customers_CurrentApplicationId] ON [Customers] ([CurrentApplicationId] ASC);
CREATE TABLE [Applications] (
[Id] INTEGER NOT NULL
, [CustomerId] bigint NULL
, CONSTRAINT [sqlite_master_PK_Applications] PRIMARY KEY ([Id])
, FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE CASCADE ON UPDATE NO ACTION
);
CREATE INDEX [IX_Applications_CustomerId] ON [Applications] ([CustomerId] ASC);
正是我们想要的。
现在,您如何查询此配置中的事件和非事件应用程序?像这样:
var customerId = 1;
using (var ctx = new DataContext())
{
var currentApplication = (
from customer in ctx.Customers
where customer.Id == customerId
select customer.CurrentApplication
).FirstOrDefault();
var pastApplications =
(
from customer in ctx.Customers
from application in customer.Applications
where customer.Id == customerId && customer.CurrentApplication != application
select application
).ToArray();
}
我建议你通读要找到的acticles here熟悉 EF Core。
关于关系数据库建模 this site似乎是有用的资源。
