本文是博主在开发某电商平台项目的一些杂项记录,方便自己和团队同事查阅,偏向于具体技术或应用的细节和个人理解,但也未必非常具体。文中未提的更多内容可能会另起篇章。
- 导航属性——EF实体关系fluent配置
- AutoMapper
- Autofac
- Repository模式
- Model & DTO
- 开源&商用.NET电商平台——NopCommerce(3.9版) & Himall(2.4版)
- 服务器搭建-VMware vSphere Hypervisor(esxi)
- 自动化部署-Jenkins
实体关系——一对一[或零],一对多,多对多——对应到数据库就是外键约束。为了性能及数据迁移考虑,在事务性要求不高的情形中,我们一般都禁用外键,但是EF中仍可保留实体关系以方便编程。
本文基于EF6.1.3版本。
EF中有两类关系:Independent association 和 Foreign Key association。在实体定义时可以看出它们的不同。
1 //这是Independent association 2 public class Order 3 { 4 public int ID { get; set; } 5 public Customer Customer { get; set; } // <-- Customer object 6 ... 7 } 8 9 //这是Foreign key association 10 public class Order 11 { 12 public int ID { get; set; } 13 public int CustomerId { get; set; } // <-- Customer ID 14 public Customer Customer { get; set; } // <-- Customer object 15 ... 16 }
很明显看到两者的差别就在于是否存在外键属性,EF会按照默认规则构建或寻找到正确的外键字段。我们也可以显式配置外键,两种方法:
1 Map(m=>m.MapKey("CustomerId")); 2 HasForeignKey(m=>m.CustomerId);
Map适用于Independent association,而HasForeignKey用于Foreign Key association。如果在Foreign Key association时使用Map,将会抛出:“CustomerId:Name:类型中的每个属性名必须唯一,已定义属性名CustomerId”的错误。
需要注意的是,一对一的实体关系,EF并未提供HasForeignKey指定外键。why?EF团队认为,既然两个实体是一一对应的关系,那么可以由一个主键标识两个实体,so,will use its primary key as the foreign key。。。也是醉了。如果硬要指定一个外键的话,对于Independent association还好,我们可以用Map,但是Foreign Key association就悲剧了。可以使用WithMany()这个hack,但比较别扭,个人是不推荐这种方法。详情可参看One to zero-or-one with HasForeignKey。尝试使用[ForeignKey]特性,也会报错[比如]:系“CategoryCashDepositInfo_CategoriesInfo”中 Role“CategoryCashDepositInfo_CategoriesInfo_Source”的多重性无效。因为 Dependent Role 属性不是键属性,Dependent Role 多重性的上限必须为“*”。so,一对一实体的外键也必须是它的主键,尼玛。不幸遇到这种问题,在项目初期(一般来说踩坑都是比较早的),最好的方式还是改变数据结构以适应EF要求,毕竟它这么要求确实有道理。
另:若一实体没有导航属性,但是另一实体包含该实体集合的属性,那么在生成数据库时,EF也会自动为它们生成外键约束。
在增删改实体时,若有上下文跟踪,则连带着实体的导航属性对应的数据也一并会受影响,比如在更新父子表时,不需要自己写单独更细两张表的代码,EF都帮我们处理好了。举个典型例子:
public class Journal { public int ID { get; set; } public decimal Amount { get; set; } public int OrderID { get; set; } public BillOrder Order { get; set; } } public class BillOrder { public int ID { get; set; } public string Title { get; set; } } using (var context = new Entities()) { var order = new BillOrder { Title="test order" }; //OrderID =order.ID 有无都一样,最后数据表里字段会赋予实际值 var j = new Journal { Amount=10, Order= order,OrderID =order.ID }; context.Journals.Add(j);//只要add主类即可 context.SaveChanges(); }
更多可参看 MVC3+EF4.1学习系列(六)-----导航属性数据更新的处理
待验证:一对一时,导航属性有没有延迟加载一说?另导航属性链查询细节,比如Comment.OrderItem.OrderInfo.PayDate,其中OrderItem是Comment的导航熟悉,OrderInfo是OrderItem的导航属性,这个时候SQL查询步骤是怎样的呢?——一对一时,不会自动加载,即获取父对象后,导航属性对应的子对象一直为null(不管后续有没有用到,用到的话会抛出NullReferenceException),但是在获取父对象时使用Include显式加载子对象,是可以的。同其它导航属性一样,之前测试出现无法加载是因为忘记给导航属性前面添加virtual关键字了。。。
导航属性的删除更新操作需要特别注意,如果直接将导航属性赋值为新对象,保存后,数据表中将新增记录,而原记录仍然存在,原因显而易见,这里不说了。
1 var order = context.BillOrders.First(); 2 context.Set<BillOrderSub>().RemoveRange(order.Items);//这步不能少 3 var items = new int[] { 1, 1, 1 }.Select(i => new BillOrderSub()).ToArray(); 4 order.Items = items; 5 context.SaveChanges();
注意要使用DbSet定义的RemoveRange之类的方法,否则会报下面的错误
另:给父对象设置EntityState,并不会自动给导肮实体赋予相同Entity