2020-12-26

博客园Logo
首页
新闻
博问
专区
闪存
班级

代码改变世界
搜索
注册
登录
返回主页
五行缺码
博客园
首页
新随笔
联系
订阅
管理
EF Core 三 、 骚操作 (导航属性,内存查询…)

EF Core 高阶操作
本文之前,大家已经阅读了前面的系列文档,对其有了大概的了解
我们来看下EF Core中的一些常见高阶操作,来丰富我们业务实现,从而拥有更多的实现选择
1.EF 内存查找

what?我们的ef不是直接连接数据库吗?我们查询的主体肯定是数据库啊,哪里来的内存呢?
1.所有的数据操作都有过程,并非操作直接会响应到数据库
2.并非所有的操作都每次提交,会存在缓存收集阶段,批量提交机制

描述下业务场景,我们存在一个业务,需要存储一张表,然后还需要对存储表数据做一些关联业务处理?我们可能会将方法拆分,首先处理数据保存,然后再根据数据去处理业务

直接看下代码

public static void Query_内存查询()
{
TestTable newTable = new TestTable();
newTable.Id = 10;
newTable.Name = “测试数据”;
using (MyDbContext dbContext = new MyDbContext())
{
dbContext.Add(newTable);
Query_内存查询_关联业务处理(dbContext);
dbContext.SaveChanges();
}
}

    private static void Query_内存查询_关联业务处理(MyDbContext dbContext)
    {
        var entity = dbContext.TestTables.FirstOrDefault(p => p.Id == 10);
        //处理业务逻辑
        //...
    }

代码运行效果:

发现并没有将数据查询出来,因为默认会查询数据库数据,此时数据还未提交,所以无法查询。但是也可以将实体数据传入到依赖方法啊,这样可以解决,但是如果关联实体多,来回传递麻烦,所以这不是最佳解

EF Core的缓存查询,前面文章已经提到,EF Core会将所有的改动存储到本地的缓存区,等待一起提交,并随即提供了基于缓存查询的方法,我们来验证下

public static void Query_内存查询()
{
TestTable newTable = new TestTable();
newTable.Id = 10;
newTable.Name = “测试数据”;
using (MyDbContext dbContext = new MyDbContext())
{
dbContext.Add(newTable);
Query_内存查询_关联业务处理(dbContext);
}
}
private static void Query_内存查询_关联业务处理(MyDbContext dbContext)
{
var entity = dbContext.TestTables.FirstOrDefault(p => p.Id == 10);
//处理业务逻辑
//…
var entity2 = dbContext.TestTables.Find(10);
//处理业务逻辑
//…
}
代码运行效果:

可以看到我们已经能够查询到未提交的数据了,但是也有必须的前提
1.必须使用ID查询,这点我们下面来分析
2.必须保证在同一上下文中,这点通过我们前面文章分析,缓存维护都是基于上下文维护,所以无法跨上下文来实现缓存数据查询

直接看源码,通过源码查看,分析得到通过Find()方法调用StateManager.FindIdentityMap(IKey key)方法

private IIdentityMap FindIdentityMap(IKey key)
{
if (_identityMap0 == null
|| key == null)
{
return null;
}

        if (_identityMap0.Key == key)
        {
            return _identityMap0;
        }

        if (_identityMap1 == null)
        {
            return null;
        }

        if (_identityMap1.Key == key)
        {
            return _identityMap1;
        }

        return _identityMaps == null
               || !_identityMaps.TryGetValue(key, out var identityMap)
            ? null
            : identityMap;
    }

这里就是对_identityMaps集合进行查找,那这个集合是什么时候有数据呢?为何新增的数据会在?看下DBContext.Add方法
DbContext.Add=>InternalEntityEntry.SetEntityState=> StateManager.StartTracking(this)=>StateManager.GetOrCreateIdentityMap
核心代码:

if (!_identityMaps.TryGetValue(key, out var identityMap))
{
identityMap = key.GetIdentityMapFactory()(SensitiveLoggingEnabled);
_identityMaps[key] = identityMap;
}
会将当前实体放入集合中,如果集合中没有查询到,那就会执行数据库查询命令

2.导航属性

通过一个实体的属性成员,可以定位到与之有关联的实体,这就是导航的用途了
业务的发生永远不会堆积在单表业务上,可能会衍生多个关联业务表上,那在这种场景下,我们就需要导航属性,还是以示例入手

首先,我们需要两个关联实体,来看下实体

[Table(“TestTable”)]
public class TestTable : EntityBase
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public ICollection TestTableDetails { get; set; }
}

[Table("TestTableDetail")]
public class TestTableDetail : EntityBase
{
    [Key] 
    public int Id { get; set; }              
    public int TestTableId { get; set; }
    public int PID { get; set; }
    public string Name { get; set; }        
}

然后我们来测试下,实现关联数据的插入

public static void Insert_导航属性_数据准备()
{
TestTable table = new TestTable();
table.Id = 10;
table.Name = “主表数据10”;
TestTableDetail detail1 = new TestTableDetail();
detail1.Id = 1;
//detail1.PID = 10;
detail1.Name = “主表数据10-从表数据1”;
TestTableDetail detail2 = new TestTableDetail();
detail2.Id = 2;
//detail2.PID = 10;
detail2.Name = “主表数据10-从表数据2”;
table.TestTableDetails = new List();
table.TestTableDetails.Add(detail1);
table.TestTableDetails.Add(detail2);
using (MyDbContext db = new MyDbContext())
{
if (db.TestTables.FirstOrDefault(p => p.Id != 10) == null)
return;
db.TestTables.Add(table);
//db.TestTableDetails.Add(detail1);
//db.TestTableDetails.Add(detail2);
db.SaveChanges();
}
}
结果:

实现了数据插入成功,这里第一个知识点。
如果要实现数据表的关联关系,一对多,必须有如下的约定
1.EFCore 默认导航属性,约定规则,主表包含从表数据集合,且从表包含主表表明+'Id’的字段
这样主,从表会被EFCore默认识别到,自动维护从表的外键信息
2.主实体包含从列表实体,以及从实体包含主实体,且从表包含从表导航属性名+主表主键名

[Table(“TestTable”)]
public class TestTable : EntityBase
{
[Key] public int Id { get; set; }
public string Name { get; set; }
public ICollection TestTableDetails { get; set; }
}

[Table("TestTableDetail")]
public class TestTableDetail : EntityBase
{
    [Key] 
    public int Id { get; set; }                      
    public int PID { get; set; }
    public string Name { get; set; }   

    public int TestId { get; set; }
    public TestTable Test { get; set; }
}

TestTableDetail中包含了导航属性Test,主实体主键为ID,那就必须包含外键TestId,看下运行效果

3.从实体包含导航属性,且包含 主表名称+主表主键 的外键字段

[Table(“TestTable”)]
public class TestTable : EntityBase
{
[Key] public int Id { get; set; }
public string Name { get; set; }
public ICollection TestTableDetails { get; set; }
}

[Table("TestTableDetail")]
public class TestTableDetail : EntityBase
{
    [Key] 
    public int Id { get; set; }                      
    public int PID { get; set; }
    public string Name { get; set; }   

    public int TestTableId { get; set; }
    public TestTable Test { get; set; }
}

三面三种方式来建立我们实体之间的主外键关系也还不错,但是往往业务中可能没有我们想象的简单,没法符合上面的三种规则,那我们就需要手动来设置导航属性
4.手动设置一,实体ForeignKey设置

public class TestTable : EntityBase
{
[Key] public int Id { get; set; }
public string Name { get; set; }
public ICollection TestTableDetails { get; set; }
}

[Table("TestTableDetail")]
public class TestTableDetail : EntityBase
{
    [Key] 
    public int Id { get; set; }                      
    public int PID { get; set; }
    public string Name { get; set; }  
    [ForeignKey("PID")]
    public TestTable Test { get; set; }
}

运行结果,可以看到我们使用了自定义的外键PID

5.手动设置二,Fluent API 设置
DbContext配置实体关系

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 映射实体关系,一对多
modelBuilder.Entity()
.HasOne(p=>p.Test)
.WithMany(p=>p.TestTableDetails)
.HasForeignKey(p=>p.PID);
}
public class TestTable : EntityBase
{
[Key] public int Id { get; set; }
public string Name { get; set; }
public ICollection TestTableDetails { get; set; }
}

[Table("TestTableDetail")]
public class TestTableDetail : EntityBase
{
    [Key] 
    public int Id { get; set; }                      
    public int PID { get; set; }
    public string Name { get; set; }     
    public TestTable Test { get; set; }
}

看下运行效果:

导航属性的几种使用方式还是要结合真正的业务来选择,但是并非所有的场景都要使用,而且要结合性能来考虑,我们来看下导航属性的实现本质

public static void Query_导航属性()
{
MyDbContext dbContext = new MyDbContext();
var test = dbContext.TestTables.Where(p=>p.Id==10).
Include(c => c.TestTableDetails).FirstOrDefault();
}
通过API Include方法,来执行导航属性查询,然后跟踪SQL如下

SELECT [t0].[Id], [t0].[Name], [t1].[Id], [t1].[Name], [t1].[PID]
FROM (
SELECT TOP(1) [t].[Id], [t].[Name]
FROM [TestTable] AS [t]
WHERE [t].[Id] = 10
) AS [t0]
LEFT JOIN [TestTableDetail] AS [t1] ON [t0].[Id] = [t1].[PID]
ORDER BY [t0].[Id], [t1].[Id]

导航属性查询时,会将关联表进行Left Join,返回一张宽表,包含两张表的全部字段,主表数据量会呈现翻倍增长
例如:主表数据1条,二级从表3条,三级从表每个10条,那就是一张三十条数据的大宽表,从数据查询以及传输来看,对性能会照成比较大的影响,所以一定要慎用
有以下几个点:
1.在不需要关联表数据时,不需要使用Include,只会查询出主表数据

var test1 = dbContext.TestTables.FirstOrDefault(p => p.Id == 10);
2.那如果可能需要关联表数据呢?能够有一种方法,在我需要关联数据的时候再去查询?
– 2.1 分段查询,我们来看下具体效果

public static void Query_导航属性()
{
MyDbContext dbContext = new MyDbContext();
//定义查询条件,并不会执行数据库查询
var query = dbContext.TestTables.Where(p => p.Id == 10);
//执行查询,但是只会查询主表数据
var test4 = query.FirstOrDefault();
//需要从表数据时,再触发查询
query.SelectMany(p => p.TestTableDetails).Load();
}
第一次查询

SELECT [t].[Id], [t].[Name]
FROM [TestTable] AS [t]
WHERE [t].[Id] = 10
第二次查询

SELECT [t0].[Id], [t0].[Name], [t0].[PID]
FROM [TestTable] AS [t]
INNER JOIN [TestTableDetail] AS [t0] ON [t].[Id] = [t0].[PID]
WHERE [t].[Id] = 10
第一次只会查询主表,第二次查询通过Inner Join,性能也远高于Left join,且只返回了TestTableDetail的数据

– 2.2 Linq to SQL 或者 Lambda Join()
通过自主决定查询数据来优化查询方式,来提高查询效率,这也是决定Left join或者Inner join的一种方式
两种方式在特定场景下还是有比较大的性能差异

left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录   
right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录  
inner join(等值连接) 只返回两个表中联结字段相等的行
关于left join的概念,left join(返回左边全部记录,右表不满足匹配条件的记录对应行返回null),那么单纯的对比逻辑运算量的话,inner join 是只需要返回两个表的交集部分,left join多返回了一部分左表没有返回的数据。sql尽量使用数据量小的表做主表,这样效率高,但是有时候因为逻辑要求,要使用数据量大的表做主表,此时使用left join 就会比较慢,即使关联条件有索引。在这种情况下就要考虑是不是能使用inner join 了。因为inner join 在执行的时候回自动选择最小的表做基础表,效率高.
– 2.3 延迟加载
1.使用 Proxies代理方式
引入Microsoft.EntityFrameworkCore.Proxies包
2.注册代理

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
//写入连接字符串
optionsBuilder.UseSqlServer(“Data Source=.\SQLSERVER;Initial Catalog=EfCore.Test;User ID=sa;Pwd=123”);
}
3.修改实体,导航属性增加 virtual 关键字

[Table(“TestTable”)]
public class TestTable : EntityBase
{
[Key] public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection TestTableDetails { get; set; }
}

[Table("TestTableDetail")]
public class TestTableDetail : EntityBase
{
    [Key]
    public int Id { get; set; }
    public int PID { get; set; }
    public string Name { get; set; }
    public virtual TestTable Test { get; set; }
}

然后直接执行查询即可

var test1 = dbContext.TestTables.FirstOrDefault(p => p.Id == 10);
var count = test1.TestTableDetails.Count();
观察SQL
第一次:

SELECT TOP(1) [t].[Id], [t].[Name]
FROM [TestTable] AS [t]
WHERE [t].[Id] = 10
第二次,访问TestTableDetails时触发

exec sp_executesql N’SELECT [t].[Id], [t].[Name], [t].[PID]
FROM [TestTableDetail] AS [t]
WHERE [t].[PID] = @__p_0’,N’@__p_0 int’,@__p_0=10
文本就先到这吧,要开始做饭了 😄 …
EF Core在使用时还是要多了解,避免使用中带来的更多问题,后续一起继续学习

分类: EF Core, .netcore
标签: EF Core
好文要顶 关注我 收藏该文
五行缺码
关注 - 7
粉丝 - 23
+加关注
1 0
« 上一篇: EF Core 三 、 EF Core CRUD
posted @ 2020-12-26 15:36 五行缺码 阅读(121) 评论(0) 编辑 收藏
刷新评论刷新页面返回顶部
登录后才能发表评论,立即 登录 或 注册, 访问 网站首页
写给园友们的一封求助信
【推荐】News: 大型组态、工控、仿真、CADGIS 50万行VC++源码免费下载
【推荐】有你助力,更好为你——博客园用户消费观调查,附带小惊喜!
【推荐】博客园x丝芙兰-圣诞特别活动:圣诞选礼,美力送递
【推荐】了不起的开发者,挡不住的华为,园子里的品牌专区
【福利】AWS携手博客园为开发者送免费套餐+50元京东E卡
【推荐】未知数的距离,毫秒间的传递,声网与你实时互动
【推荐】新一代 NoSQL 数据库,Aerospike专区新鲜入驻

相关博文:
· EFcoremysql
· EFCore批处理语句
· ASP.NETCore2.1使用EFCore操作MySql数据库
· .NetCore3骚操作之用Windows桌面应用开发Asp.NetCore网站
· C#之.netcore–EFcodefirst连接Mysql数据库
» 更多推荐…

最新 IT 新闻:
· 熬过2020,我的公司还活着
· 一夜之间,两家A股网游上市公司实控人出大事了!
· 苹果造车,到底有没有戏?
· 玩了10小时《赛博朋克2077》 我觉得很失望
· 一文读懂2020年大厂如何调整组织架构
» 更多新闻…
公告
昵称: 五行缺码
园龄: 7年
粉丝: 23
关注: 7
+加关注
< 2020年12月 >
日 一 二 三 四 五 六
29 30 1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31 1 2
3 4 5 6 7 8 9
搜索

找找看

谷歌搜索
我的标签
Docker(4)
EF Core(4)
数据库(3)
ABP(2)
autofac ioc di(1)
C# .net Email QQ 163(1)
.NET C# 按需加载 延迟加载 性能优化(1)
.netcore asp.net nginx supervisor(1)
.netcore asp.net nuget(1)
.netcore signalr redis 负载均衡(1)
更多
随笔分类
.netcore(8)
ABP(2)
Angularjs(1)
asp.net core(1)
Docker(5)
dojo(1)
EF Core(4)
Sonar(1)
Web Api(2)
WF(15)
数据库(3)
随笔档案
2020年12月(1)
2020年11月(3)
2020年10月(2)
2020年9月(8)
2020年8月(1)
2018年12月(1)
2018年4月(1)
2018年3月(8)
2017年4月(2)
2017年2月(1)
2016年4月(1)
2015年7月(1)
2014年10月(12)
2014年9月(3)
2014年8月(11)
最新评论

  1. Re:EF Core 二 、 入门 EF Core
    谢谢分享
    –沈赟
  2. Re:EF Core 二 、 入门 EF Core
    @流光_…
    –五行缺码
  3. Re:EF Core 二 、 入门 EF Core
    啥“当然Code First也不会丢掉的”,楼主大胆点,就喜欢DbFirst的粗暴直接
    –流光_
  4. Re:EF Core 一、重识 EF
    感谢分享,可以以EFCore为主,辅以FreeSql, SqlSugar, dapper
    –WebAssembly
  5. Re:EF Core 一、重识 EF
    还记得手码Connection,command的时候,那时候orm就是自己封装的一套,现在现成的好用的orm框架太多了…
    –五行缺码
    阅读排行榜
  6. 代码检查工具 Sonar 安装&使用(5805)
  7. asp.net core 五 SignalR 负载均衡(1886)
  8. Web API 之承载宿主IIS,SelfHost,OwinSelfHost(1764)
  9. C# QQ & 163 邮件发送(1589)
  10. Web API 之SelfHost与OwinSelfHots加载外部程序(951)
    评论排行榜
  11. Web API 之SelfHost与OwinSelfHots加载外部程序(13)
  12. ABP 数据访问 - IRepository 仓储(5)
  13. EF Core 一、重识 EF(4)
  14. asp.net core 五 SignalR 负载均衡(4)
  15. EF Core 二 、 入门 EF Core(3)
    推荐排行榜
  16. asp.net core 五 SignalR 负载均衡(12)
  17. EF Core 二 、 入门 EF Core(5)
  18. Docker:四、Docker进阶 Windows Docker IIS 部署(4)
  19. ABP 数据访问 - IRepository 仓储(3)
  20. Web API 之SelfHost与OwinSelfHots加载外部程序(3)
    Copyright © 2020 五行缺码
    Powered by .NET 5.0.1-servicing.20575.16 on Kubernetes
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值