Entity Framework Code First 学习日记(7)-多对多关系
上一次的日记中我们详细讨论了Entity Framework Code First如何建立表之间的一对多关系。这次的日记中我们将详细介绍Entity Framework Code First建立多对多关系的默认行为,以及如何通过Fluent API改变默认行为。
本次日记主要介绍一下内容:
1.Entity Framework Code First在什么情况下会建立表之间的多对多关系,以及建立多对多关系时的默认配置。
2.如何通过Fluent API更改Entity Framework Code First建立多对多关系时的默认配置。
1.Entity Framework Code First在什么情况下会建立表之间的多对多关系,以及建立多对多关系时的默认配置。
假设我们的业务领域再次发生了改变,客户要求我们记录每种产品的打折信息。根据我们和客户的交谈,我们可以得到如下的业务逻辑:
每个产品种类需要保留所有和它相关的打折促销记录。每次打折促销都有固定的开始时间,结束时间,促销的产品列表以及打折的折扣率。
我们根据这个业务领域的变化重新改写了我们的ProductCatalog类:
public class ProductCatalog { public int ProductCatalogId { get; set; } public string CatalogName { get; set; } public string Manufactory { get; set; } public decimal ListPrice { get; set; } public decimal NetPrice { get; set; } public List<Product> ProductInStock { get; set; } public List<SalesPromotion> SalesPromotionHistory { get; set; } public ProductCatalog() { SalesPromotionHistory = new List<SalesPromotion>(); ProductInStock = new List<Product>(); } public Product GetProductInStock() { if (ProductInStock.Count <= 0) { throw new Exception("No product in stock"); } Product product = ProductInStock[0]; ProductInStock.RemoveAt(0); return product; } public void PurchaseProduct(List<Product> products) { ProductInStock.AddRange(products); } public void PurchaseProduct(List<Product> products,decimal newListPrice,decimal newNetPrice) { ProductInStock.AddRange(products); ListPrice = newListPrice; NetPrice = newNetPrice; } public decimal GetPromotionPrice() { if (SalesPromotionHistory.Count <= 0) { return ListPrice; } decimal listPrice = 0; foreach (var promotion in SalesPromotionHistory) { if (promotion.StartDate <= DateTime.Now && promotion.EndDate >= DateTime.Now) { listPrice = ListPrice * promotion.SalesDiscount; } } return listPrice; } }
我们也根据业务领域定义了我们的SalesPromotion类
public class SalesPromotion { public int SalesPromotionId { get; set; } public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } public decimal SalesDiscount { get; set; } public List<ProductCatalog> PromotionProductCatalog { get; set; } public SalesPromotion() { PromotionProductCatalog = new List<ProductCatalog>(); } public void AddProductCatalogToPromotion(ProductCatalog catalog) { catalog.SalesPromotionHistory.Add(this); PromotionProductCatalog.Add(catalog); } }
我们可以写一次单元测试方法,测试一下Entity Framework Code First会根据类的定义建立出怎样的数据表关系。
[TestMethod] public void CanAddSalesPromotion() { OrderSystemContext unitOfWork = new OrderSystemContext(); ProductRepository repository = new ProductRepository(unitOfWork); ProductCatalog catalog = repository.GetProductCatalogById(1); SalesPromotion promotion = new SalesPromotion { StartDate = DateTime.Parse("2013-1-18"), EndDate = DateTime.Parse("2013-1-25"), SalesDiscount = 0.75M }; promotion.AddProductCatalogToPromotion(catalog); unitOfWork.CommitChanges(); }
我们打开数据库看一下,看看Entity Framework Code First从类的定义映射出来的数据库结构是怎样的?
大家可以看到由于我们的SalesPromotion和ProductCatalog类中都包含对方类的实例集合,在这种情况下,Entity Framework Code First默认地会将类之间的引用关系映射为数据库表之间的多对多关系。Entity Framework会建立一个多对多的连接表,表的名字是两个关联表的名字加在一起,然后按照它自己的规则去给你加上复数形式。表中的两个字段,既是引用两个关联表的外键,并且也作为这个新的连接表的联合主键。
但是大家可能会发现连接表的名字和连接表中字段的名字都是Entity Framework Code First按照自己的规则定义的,我们可以对它们进行修改。
2.如何通过Fluent API更改Entity Framework Code First建立多对多关系时的默认配置
我们可以通过上一次日记我们介绍的Has和With方法去配置连接表的名字和连接表中两个外键列的名字。
public class SalesPromotionEntityConfiguration:EntityTypeConfiguration<SalesPromotion> { public SalesPromotionEntityConfiguration() { Property(p => p.SalesDiscount).HasPrecision(18, 4); HasMany(p => p.PromotionProductCatalog) .WithMany(ca => ca.SalesPromotionHistory) .Map(r => { r.ToTable("ProductCatalogSalesPromotion"); r.MapLeftKey("PromotionId"); r.MapRightKey("CatalogId"); } ); } }
由于我们是对多对多关系产生的数据库结构进行配置,所以我们需要使用HasMany WithMany。然后我们通过Map方法,可以指定多对多连接中连接表的名字以及连接表中两个联合主键即外键的名字。
我们重新执行我们的单元测试程序,我们可以发现新建的连接表和列的名字都是我们设置的名字。
下一篇日记是Entity Framework Code First映射数据表之间关系的最后一篇:一对一关系。