如何为其中一个是活动项目的项目集合建模?

时间:2018-08-17 22:30:36

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

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

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

2 个答案:

答案 0 :(得分:3)

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

首先,您需要确定客户与应用程序之间的关系是什么

  • 一对多,即一个客户可能有零个或多个应用程序,但一个应用程序恰好属于一个客户。此场景也称为主从关系。 “多方”实体(在这种情况下为“应用程序”)存储对其所有者(客户)的引用(称为外键)。
  • 多对多,即一个客户可能有零个或多个应用程序,但一个应用程序也可能属于零个或多个客户。这种情况是使用额外的“联接”表(通常称为CustomerApplication之类的表)进行建模的,因此最终将问题解决为两个“一对五”关系。

如果在给定时间(每个客户)只有一个活动的应用程序,则可以使用客户与应用程序之间的一对一(准确地说是零对一)关系来对活动应用程序进行建模。客户方的外键)。您也可以尝试在应用程序上使用标志字段对它进行建模,但是它不能像外键一样具有防错功能(但是可能具有更好的性能)。

您发布的代码非常类似于一对多的情况,因此我为这种情况提供了一个示例。了解以下内容后,您可以根据需要轻松地将其更改为“多对多”。

首先让我们定义实体:

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。我们需要为零对多关系明确定义外键(稍后将对此进行详细介绍)。可为null的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();
}

我建议您通读here的方法以熟悉EF Core。

对于关系数据库建模,this site似乎是有用的资源。

答案 1 :(得分:0)

使用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;

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