C# EF实体操作汇总
一、配置外键关系
(1)DBFirst配置:
在数据库比如sqlserver中设置好外键,同步到模型中。
一个问题需要注意——
模板自动生成的导航属性默认是开启了延迟加载的,就像这样👇
public partial class TimerServices_Base { [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] public TimerServices_Base() { this.TimerServices_HoldDetail = new HashSet<TimerServices_HoldDetail>(); } public long Id { get; set; } public string Name { get; set; } // ……
// 重点在“virtual”关键字 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public virtual ICollection<TimerServices_HoldDetail> TimerServices_HoldDetail { get; set; } }
重点在“virtual”关键字,带上即开启延迟加载。
就是说在LINQ查询中不使用inclued也会自动进行联表查询,神烦的是可能会出现循环套娃的情况——
// 比如Base表和Detail表呈1:m对应关系,二者分别定义了对方的导航属性,如下: public class Base { // …… public virtual ICollection<Detail> Detail { get; set; } } public class Detail { // …… public virtual Base Base { get; set; } } // 此时我想单独查询Detail的信息 var res = _dbEntities.Detail.ToList();
得出的结果大概是这样子的——大概是A中有B,B中还有A这么个情况
[{ "Detail": [{ "DetailId": "001", "DetailName": "xxx", "Base": { "BaseId": "001", "BaseName": "xxx", "Detail": [{ "DetailId": "001", "DetailName": "xxx" }] } }] }]
就很想要用include则联表,不用include不联表,该怎么设置呢?
简单,去掉virtual关键字,关闭延迟加载。
一切就恢复正常了!
使用建议:在查询中不涉及单独查询,一定需要join的表的可加上virtual,其他可加可不加就尽量不加,以免影响查询效率
更多内容参考官方文档:https://learn.microsoft.com/zh-cn/ef/ef6/querying/related-data
(2)手动配置参考
二、联表查询
假如存在三张表:
学生表Student(StudentId,Name,Age,CourseId)
课程表Course(CourseId,Name)
教辅用书表Books(BookId,Name)
对应关系:一个学生可选多门课程,每门课程对应多本教辅用书,即1:m:n关系
(1)Join
两个表不必含有外键关系。
需要代码手动指定连接外键相等(具有可拓展性,除了值相等,还能指定是>,<以及其他对两表的相应键的关系),以及结果字段。
var res= dc.Student.Join(dc.Course, a => a.CourseId, g => g.CourseId, (a, g) => new { a.StudentId,a.Name,g.CourseId,g.Name;
这样就获取到了两表的{ StudentId,Name,CourseId,Name}。
(2)Include
两表必须设置外键关系(设置方法参考一)
只需要指定键名对应的类属性名,不需要指定结果字段(即全部映射)。
默认搜索某表时,不会顺带查询外键表,直到真正使用时才会再读取数据库查询;若是使用 Include(),则会在读取本表时把指定的外键表信息也读出来。
//EF已经生成了Student和Course的数据库映射模型类以及导航属性 var res= dc.Student.Include("Course").ToList(); //或者 //var res= dc.Student.Include(e=>e.Course).ToList();
这样数据库就执行了一个左连接,把Student和Course的所有字段全部连起来了,
并且Include()是立即查询的,像ToList()一样,不会稍后延迟优化后再加载。
这样其实效率很低,因为如果两张表记录很多,那么连接是个费时费资源的事情,建议少用,或者先筛选出需要的结果集再连接。
(3)ThenInclude
一般在需要多级联表的时候使用,可以一次拿取多层级数据。
还是参考前面的三张表,比如想一次性获取学生信息、选课信息,同时要知道这名学生需要购买哪些教辅用书——返回给我一个三层数据的Json。
此时就到了ThenInclude()出场——
// 用include:拿到学生&选课信息 // 因为Student表和Course表没有直接关联关系,所以只能拿到两级数据 var res1 = dc.Student.Include(e => e.Course).ToList();
// 加上ThenInclude:Student先联Course,再用二者结果联Books,拿到三级数据 var res2 = dc.Student.Include(e => e.Course).ThenInclude(a => a.Books).ToList();
ps:需要注意,ThenInclude() 方法在EFCore中才有,项目框架.NET5(及以下)只适配EntityFramework,用不了。
三、数据操作:增删改查、分组、交并集
tbc.
四、异常记录&解决方法
(1)无法在 LINQ to Entities 查询中构造实体或复杂类型
异常如图:
异常提示:
问题描述: TimerServices_HoldDetail是EF映射的实体类,使用LINQ过滤已删除的记录,返回一个List<TimerServices_HoldDetail>的值。但是运行后报错,The entity or complex type 'MESWebDBModel.TimerServices_HoldDetail' cannot be constructed in a LINQ to Entities query.
错误代码:
var res = _dbEntities.TimerServices_HoldDetail.Where(e => e.IsDeleted == 0).Select(e => new TimerServices_HoldDetail { Id = e.Id, CreateTime = e.CreateTime, CreateUser = e.CreateUser, }).ToList();
原理: linq 选择数据时候 不能new 已知的对象,只能匿名的。 但是如果从一个 List 列表 就可以new 已知的类。
EF 里面是不能直接new出它自己的实体类。可以先查询出匿名类,.ToList()之后再new。
正确代码:
var res = _dbEntities.TimerServices_HoldDetail.Where(e => e.IsDeleted == 0).ToList().Select(e => new TimerServices_HoldDetail { Id = e.Id, CreateTime = e.CreateTime, CreateUser = e.CreateUser, }).ToList();
(2)EF 查询视图返回重复数据
问题描述:Linq查询视图返回的结果集条数正常,但是!全部是同一条数据。
异常代码:
var res = _dbEntities.View_Reports.Where(e => e.Id.ToString() == id).ToList();
原因:在数据库设计的理念中每个表都应该的唯一的主键。但视图不同,EF中会自动按视图的最前几个非空型字段设置为主键。
如果在某些特殊的查询情况下。前几列数据一致时,EF就会返回重复数据。
解决办法:在使用的视图后 加入 AsNoTracking 阻止EF缓存数据集。(EF会依据主键建立数据缓存,实现后续的级联操作)。
修正代码:
var res = _dbEntities.View_Reports.AsNoTracking().Where(e => e.Id.ToString() == id).ToList();