EF代码优先:一对多两次到相同的集合类型

Har*_*lse 6 c# many-to-many entity-framework

简化:在我的数据库中,我有一种产品在不同日期以不同价格出售。换句话说,它有一个价格历史记录。我有两个类:具有一对多关系的产品价格:

public class Product
{
    public int ProductId {get; set;}
    public string Name {get; set;}

    public ICollection<Price> Prices {get; set;}
}

public class Price
{
    public int PriceId {get; set;}

    // foreign key to Product:
    public int ProductId {get; set;}
    public Product Product {get; set;}

    public DateTime ActivationDate {get; set;}
    public decimal value {get; set;}
}

public class MyDbContext : DbContext
{
    public DbSet<Price> Prices { get; set; }
    public DbSet<Product> Products { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

到目前为止一切顺利,实体框架知道如何处理这个问题。通过使用这两个类,我可以获得特定日期特定产品的价格。

但是,如果我的产品有两个价格历史记录:购买价格历史记录和零售价格历史记录怎么办?

public class Product
{
    public int ProductId {get; set;}
    public string Name {get; set;}

    public ICollection<Price> PurchasePrices {get; set;}
    public ICollection<Price> RetailPrices {get; set;}  
}
Run Code Online (Sandbox Code Playgroud)

因为这两个集合属于同一类型,所以我不希望单独的表填充相同类型的对象(真正的原因:我有很多带有价格集合的类)。

所以我必须使用 Fluent API 进行一些编码。我的直觉告诉我,我需要连接表,就像在多对多关系中一样,可以使用 ManyToManyNavigationPropertyConfiguration.Map。

这个怎么做?

Har*_*lse 4

在阅读了有关一对一外键关联的故事并将其用于一对多关联后,我能够按照以下要求来实现它:

  • 我可以有许多不同的类具有相同类型的属性 T
  • 类型 T 的所有实例都可以放入一张表中,即使该类型的“所有者”位于不同的表中。
    • 一个类甚至可以有两个 T 类型的属性。

例如:客户可能有 BillingAddress 和 DeliveryAddress,两者都是地址类型。两个地址可以放在一张表中:Address。

public class Address
{
    public int Id { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string ZipCode { get; set; }
}

public class User
{
    public int UserId { get; set; }
    public string Name { get; set; }

    public int BillingAddressId { get; set; }
    public Address BillingAddress { get; set; }
    public int DeliveryAddressId { get; set; }
    public Address DeliveryAddress { get; set; }
}

public class MyDbContext : DbContext
{
    public DbSet<Address> Addresses { get; set; }
    public DbSet<User> Users { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>()
            .HasRequired(p => p.DeliveryAddress)
            .WithMany()
            .HasForeignKey(p => p.DeliveryAddressId)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<User>()
            .HasRequired(p => p.BillingAddress)
            .WithMany()
            .HasForeignKey(p => p.BillingAddressId)
            .WillCascadeOnDelete(false);
    }
}
Run Code Online (Sandbox Code Playgroud)

该解决方案的聪明之处在于该地址中没有“拥有”用户。因此,如果我定义一个带有地址的新类,则可以将该地址添加到同一个地址表中。因此,如果我有十个不同的类,它们都有一个地址,我不需要十个地址表。

如果您有地址集合该怎么办?

通常在一对多关系中,多方需要一侧的外键以及对“所有者”的引用:

一个常见的例子:博客和帖子:一个博客有很多帖子。一篇文章恰好属于一个博客:

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
    virtual public ICollection<Post> Posts {get; set;}
}

public class Post
{
    public int Id { get; set; }
    public string Text { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
 }
Run Code Online (Sandbox Code Playgroud)

此命名将自动导致正确的一对多关系,但如果您想在 DbContext 中指定:

public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
Run Code Online (Sandbox Code Playgroud)

并在 OnModelCreating 中:

modelBuilder.Entity<Blog>()
    .HasMany(b => b.Posts)
    .WithRequired(post => post.Blog)
    .HasForeignKey(post => post.BlogId);
Run Code Online (Sandbox Code Playgroud)

即使您不需要 Post.Blog,您也无法删除此属性,因为模型正在创建。如果您将其删除,您最终会得到神奇的字符串来定义外键。

为了还能够拥有地址集合(或者在我最初的问题中:大量价格历史记录,其中每个价格历史记录都是价格的集合),我结合了这两种方法。

public class Price
{
    public int Id { get; set; }
    public int PriceHistoryId { get; set; }
    public virtual PriceHistory PriceHistory { get; set; }

    public DateTime ActivationDate { get; set; }
    public decimal Value { get; set; }
}

public class PriceHistory
{
    public int Id { get; set; }
    virtual public ICollection<Price> Prices { get; set; }
}

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }

    // Purchase Prices
    public virtual PriceHistory PurchasePriceHistory { get; set; }
    public int PurchasePriceHistoryId { get; set; }

    // Retail prices
    public virtual PriceHistory RetailPriceHistory { get; set; }
    public int RetailPriceHistoryId { get; set; }
}

public class MyDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    public DbSet<PriceHistory> PriceHistories { get; set; }
    public DbSet<Price> Prices { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // one price history has many prices: one to many:
        modelBuilder.Entity<PriceHistory>()
            .HasMany(p => p.Prices)
            .WithRequired(price => price.PriceHistory)
            .HasForeignKey(price => price.PriceHistoryId);

        // one product has 2 price histories, the used method is comparable
        // with the method user with two addresses
        modelBuilder.Entity<Product>()
            .HasRequired(p => p.PurchasePriceHistory)
            .WithMany()
            .HasForeignKey(p => p.PurchasePriceHistoryId)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<Product>()
            .HasRequired(p => p.RetailPriceHistory)
            .WithMany()
            .HasForeignKey(p => p.RetailPriceHistoryId)
            .WillCascadeOnDelete(false);
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经用具有多个价格历史记录的其他类对其进行了测试: - 所有价格都将在一张表中 - 所有价格历史记录将在一张表中 - 对价格历史记录的每个引用都需要一个priceHistoryId。

如果仔细观察结果,它实际上是多对多关系的实现,其中价格历史记录是耦合表。

我尝试删除 PriceHistory 类,并让 Product 在 OnModelCreating 中具有多个具有多对多的价格集合,但这会导致带有魔术字符串的“Map”语句,以及每个 PriceHistory 的单独表。

有关如何实现多对多的说明的链接