【问题标题】:How to model a collection of items where one of them is the active item?如何为其中一个是活动项目的项目集合建模?
【发布时间】:2018-08-17 22:30:35
【问题描述】:

我有一个实体有一个不活动的子列表的情况。此外,它们还有另一个相同类型的“当前/活动”子实体。

以下将是理想的建模,但我无法弄清楚如何使用 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 的功能不匹配,有什么好的实用建议?

【问题讨论】:

  • 首先,我对EF Core了解不多,但我的想法是:Customer和Application是什么关系?一个应用程序可以属于多个客户吗?如果是这样,那么IsCurrent 属性将不起作用,因为它可能对一个客户来说是最新的,而不是对另一个客户来说。 IsCurrent 在哪里/如何设置?如果它是通过Customer 类设置的,那么您只需删除Application 上的属性并将指向当前Application 的指针添加到Customer.CurrentApplication 属性。

标签: c# domain-driven-design entity-framework-core


【解决方案1】:

我不认为这是一个 DDD 问题,而是一个如何设计关系数据库模型以及如何使用 EF Core 的问题。

首先您需要确定客户和应用程序之间的关系:

  • 一对多,即一个客户可能有零个或多个应用程序,但一个应用程序只属于一个客户。这种场景也称为主从关系。多方实体(本例中为应用程序)存储对其所有者(客户)的引用(称为外键)。
  • 多对多,即一个客户可能有零个或多个应用程序,但一个应用程序也可能属于零个或多个客户。此场景使用额外的“连接”表(通常命名为 CustomerApplication)建模,因此问题最终解决为两个一对一关系。

如果在给定时间(每个客户)只有一个活动应用程序,则可以使用客户和应用程序(与客户方的外键)。也可以在您尝试使用 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? CurrentApplicationId。我们需要为我们的零对多关系明确定义外键(稍后会详细介绍)。可为空的 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 如何将实体映射到 DB 表的必备内容。

基于映射,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();
}

我建议您通读 here 的文章以熟悉 EF Core。

至于关系数据库建模this site 似乎是一个有用的资源。

【讨论】:

  • 谢谢,这行得通。我设置了类似的东西,但从 ef core 得到错误
【解决方案2】:

使用 EFCore,以下将起作用:

public class Customer 
{
    [Key]
    public int ID {get; set;}
    //other properties
    //Navigation Property
    public virtual ICollection<Application> Applications{ get; set; }
}

public class Application
{
    [Key]
    public int ID {get; set;}
    [ForeignKey("Customer")]
    public int CustomerID{get; set;}
    public DateTime ApplicationDate{get; set}
    //other properties
    public bool IsCurrent { get; set; }
}

我假设您使用的是 Code First,因此这将是进行映射的正确方法。

迁移和更新上下文后,您可以使用一些后端代码来始终确保您最近的应用程序返回为 IsCurrent。

然后您可以按如下方式选择您当前的应用程序:

 private yourContext _context;

var yourContext = _context.Customers
                .Include(m=>m.Application)
                .Where(m=> m.isCurrent==false)
                .Single(a=>a.);

            return yourContext;

您当然必须使用上下文等设置构造函数

【讨论】:

    猜你喜欢
    • 2019-07-12
    • 1970-01-01
    • 1970-01-01
    • 2014-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-13
    相关资源
    最近更新 更多