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)手动配置参考

EF Core 数据库实体关系外键的配置

Ef Core 使用Entity方式配置外键

 

二、联表查询

假如存在三张表:

学生表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();

 

posted @ 2022-10-21 09:17  暴躁老砚  阅读(1255)  评论(0编辑  收藏  举报