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

时间:2016-02-18 08:46:44

标签: c# entity-framework many-to-many

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

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; }
}

到目前为止,Entity Framework知道如何处理这个问题。通过使用这两个类,我可以在某个特定日期获得某个产品的价格。

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

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;}  
}

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

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

怎么做?

3 个答案:

答案 0 :(得分:0)

目前,由于EF命名约定,您的代码正在运行:

  

Code First推断,如果类上的属性名为“ID”(不区分大小写),或者类名后跟“ID”,则属性是主键。如果主键属性的类型是数字或GUID,则它将被配置为标识列。

EF看到你有一对多,所以它自动将ProductId作为外键。如果要定义同一实体的多个集合,则必须手动定义外键。

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

   public int ProductPurchaseId {get; set;}
   public Product ProductPurchase {get; set;}

   public int ProductRetailId {get; set;}
   public Product ProductRetail {get; set;}

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

流利的api:

modelBuilder<Product>().HasMany(p => p.PurchasePrices)
                       .WithRequired(p => p.ProductPurchase)
                       .HasForeignKey(p => p.ProductPurchaseId);

modelBuilder<Product>().HasMany(p => p.RetailPrices)
                       .WithRequired(p => p.ProductRetail)
                       .HasForeignKey(p => p.ProductRetailId);

这当然意味着您需要在Product表中有{2}个外键{/ 1}}。

答案 1 :(得分:0)

根据我对您的要求的理解,您需要在价格表中添加一个额外字段,该字段将告诉您要存储的价格类型。例如:

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

    // The PriceType will recognise among different type of price- Sell Price,          Purchase Price etc.

    public string PriceType{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;}
}

答案 2 :(得分:0)

在阅读了关于one-to-one foreign key associations的故事并将其用于一对多关联后,我能够按照以下要求实现它:

  • 我可以有许多不同的类,其属性类型为T
  • 所有类型T的实例都可以放在一个表中,即使&#34;所有者&#34;这种类型的表在不同的表中。
    • 类甚至可以有两个T类型的属性。

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

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);
    }
}

这个解决方案中的聪明之处在于地址没有拥有&#34;拥有&#34;用户在里面。因此,如果我使用Address定义一个新类,则可以将该地址添加到同一个Address表中。所以如果我有十个不同的班级都有地址我不需要十个地址表。

如果您有一组地址怎么办?

通常在一对多的关系中,许多方需要一方的外键加上对#34;所有者的引用&#34;:

一个经常看到的例子:博客和帖子:一个博客有很多帖子。一个帖子只属于一个博客:

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; }
 }

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

public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }

并在OnModelCreating:

modelBuilder.Entity<Blog>()
    .HasMany(b => b.Posts)
    .WithRequired(post => post.Blog)
    .HasForeignKey(post => post.BlogId);

即使您不需要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);
    }
}

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

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

我试图删除PriceHistory类,让一个产品在OnModelCreating中有多个具有多对多的价格集合,但这会导致&#34; Map&#34;带有魔术字符串的语句,以及每个PriceHistory的单独表格。

Link to explanation about how to implement many-to-many