博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
EF Code First 一对多、多对多关联,如何加载子集合?
阅读量:7050 次
发布时间:2019-06-28

本文共 8158 字,大约阅读时间需要 27 分钟。

应用场景

先简单描述一下标题的意思:使用 EF Code First 映射配置 Entity 之间的关系,可能是一对多关系,也可能是多对多关系,那如何加载 Entity 下关联的 ICollection 集合对象呢?

上面的这个问题,我觉得大家应该都遇到过,当然前提是使用 EF Code First,有人会说,在 ICollection 集合对象前加 virtual 导航属性,比如:

public virtual ICollection
Roles { get; set; }

然后在 DbContext 初始化的时候,增加懒加载(或延迟加载)配置:

public UserDbContext()    : base("name=UserDbContext"){    this.Configuration.LazyLoadingEnabled = false;}

这种方式当然可以,也是我们常用的一种方式,但这种方式在一种场景中无法使用,就是对关联 ICollection 集合增加 Where 条件,什么意思呢?我下描述一下用户-角色应用场景,一个用户有多个权限,一个权限也可能对应多个用户,所以用户和角色之间的关系是多对多,我们用 EF Code First 进行实现一下:

User(用户)和 Role(角色)实体类:

namespace UserRoleDemo.Entities{    public class User    {        public int Id { get; set; }        public string Name { get; set; }        public string Age { get; set; }        public string Address { get; set; }        public DateTime DateAdded { get; set; }        public virtual ICollection
Roles { get; set; } } public class Role { public int Id { get; set; } public string Name { get; set; } public DateTime DateAdded { get; set; } public virtual ICollection
Users { get; set; } }}

UserRoleDbContext 映射配置:

public class UserRoleDbContext : DbContext    {        public UserRoleDbContext()            : base("name=UserRoleDb")        {            //this.Configuration.LazyLoadingEnabled = false;        }        public virtual DbSet
Users { get; set; } public virtual DbSet
Role { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder .Configurations .Add(new UserConfiguration()) .Add(new RoleConfiguration()); base.OnModelCreating(modelBuilder); } public class UserConfiguration : EntityTypeConfiguration
{ public UserConfiguration() { HasKey(c => c.Id); Property(c => c.Id) .IsRequired() .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); HasMany(t => t.Roles) .WithMany(t => t.Users) .Map(m => { m.ToTable("UserRole"); m.MapLeftKey("UserId"); m.MapRightKey("RoleId"); }); } } public class RoleConfiguration : EntityTypeConfiguration
{ public RoleConfiguration() { HasKey(c => c.Id); Property(c => c.Id) .IsRequired() .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); } } }

生成对应数据库:

可以看到,我们项目中只有 User 和 Role 两个实体对象,但是生成数据库多了一个 UserRole 表,这个是我们在 UserConfiguration 进行映射配置的结果,当然你不配置也可以,EF Code First 会自动帮你映射,但映射关联表的名字和字段就不能自定义了,如果你深入使用 EF Code First 你会越发觉得它的强大之处,因为它会让你感受不到数据库的“存在”,在应用程序中,所有都是对象之间的操作,没有了事务脚本模式的代码,你可以专注于应用对象的“研究”,即使再复杂的映射配置,EF Code First 也会帮你完成。比如这样一段代码:user.Roles,如果常规的方式(SQL),你会去在应用程序中编写“User join UserRole”的 SQL 代码,但是如果使用 EF Code First,只要映射配置正确,直接 user.Roles 就可以了,当然它不仅如此。

咳咳,扯的有点远了,有点像为微软打广告的意思,呵呵。

言归正传,用户角色的场景就这么简单,上面我说过不能使用懒加载方式解决的问题,比如我要获取一个 User 对象,但在访问 user.Roles 集合的时候,Roles 集合中 Role 对象的 DateAdded 必须大于昨天。这个就不能使用懒加载方式了,因为必须要在 user.Roles 去编写 Where 条件,而懒加载方式是获取所有关联对象的集合,怎么解决这个实际问题呢?请看下面。

问题分析

查询场景:获取 Id 为 1 的 User 对象,并且 User 下的 Roles 集合的 DateAdded 大于昨天。

问题很简单,就是这段话怎么翻译成代码?或者怎么用 Linq 的方式写出来?

有人可能会想到 Include,但使用这种方式就没必要 user.Roles 了,这种方式不可取,然后我再网上找了另一种方式,使用 Any 或 All,大致代码如下:

using (var context = new UserRoleDbContext()){    var user = context.Users        .Where(u => u.Id == 1)        .Where(u => u.Roles.All(r => r.DateAdded > DateTime.Now.AddDays(-1)))        .FirstOrDefault();    foreach (var role in user.Roles)    {        Console.WriteLine(role.DateAdded);    }}

使用 Sql Server Profiler 跟踪生成的 SQL 代码,就会发现,我们写的 DateAdded > DateTime.Now.AddDays(-1) 条件会出现在 User 获取中,下面 user.Roles 遍历的时候,还是会加载关联下的所有集合对象,当然这种方式使用必须要开启懒加载。

我个人觉得,这个问题应该在很多应用场景中都会出现,但遗憾的是网上实在找不到响应的解决方案(映射配置的比较多,但获取方式的基本上没有),当然不是说没有方式解决,最简单的就是把集合全部加载出来,然后在内存中进行过滤,项目简单的还好,如果数据量非常大,这种方式也是不可取的,最后在 MSDN 上找到一篇很多年的博客:,注意 EF 版本是 4.1,现在 7.0 都快出来了,哎!

看到“Loading Related Entities”这个标题,我就知道这篇博客就是我想要的,然后按照它描述的,配置如下:

首先,禁止懒加载:

this.Configuration.LazyLoadingEnabled = false;

Linq 查询代码:

using (var context = new UserRoleDbContext()){    var user = context.Users        .Where(u => u.Id == 1)        .FirstOrDefault();    context.Entry(user)        .Collection(u => u.Roles)        .Query()        .Where(r => r.DateAdded > DateTime.Now.AddDays(-1))        .Load();    foreach (var role in user.Roles)    {        Console.WriteLine(role.DateAdded);    }}

先说明一下,这段代码是不能运行的,因为 user.Roles 集合的值为 null,至于原因,我是后来才知道的,这种方式只适用于“一对多”的关系,哪篇博客中的演示场景也是“一对多”,如果我们把 Query() 和后面的 Where 代码去掉,没有了条件查询,这段代码时可以运行的,至于原因,我觉得没有了 where,那和懒加载又有什么区别呢。

“一对多”的方式是这种,那“多对多”的呢?答案是在 Collection 后加 Include,示例代码:

using (var context = new UserRoleDbContext()){    var user = context.Users        .Where(u => u.Id == 1)        .FirstOrDefault();    context.Entry(user)        .Collection(u => u.Roles)        .Query()        .Include(r => r.Users)        .Where(r => r.DateAdded > DateTime.Now.AddDays(-1))        .Load();    foreach (var role in user.Roles)    {        Console.WriteLine(role.DateAdded);    }}

这种方式确实是可以运行成功的,也是我们想要的效果,但如果你看一下跟踪生成的 SQL 代码,你就不想使用它了,为什么?我们看一下生成的 SQL 代码:

SELECT     [Project1].[UserId] AS [UserId],     [Project1].[RoleId] AS [RoleId],     [Project1].[Id] AS [Id],     [Project1].[Name] AS [Name],     [Project1].[DateAdded] AS [DateAdded],     [Project1].[C1] AS [C1],     [Project1].[Id1] AS [Id1],     [Project1].[Name1] AS [Name1],     [Project1].[Age] AS [Age],     [Project1].[Address] AS [Address],     [Project1].[DateAdded1] AS [DateAdded1]    FROM ( SELECT         [Extent1].[UserId] AS [UserId],         [Extent1].[RoleId] AS [RoleId],         [Extent2].[Id] AS [Id],         [Extent2].[Name] AS [Name],         [Extent2].[DateAdded] AS [DateAdded],         [Join2].[Id] AS [Id1],         [Join2].[Name] AS [Name1],         [Join2].[Age] AS [Age],         [Join2].[Address] AS [Address],         [Join2].[DateAdded] AS [DateAdded1],         CASE WHEN ([Join2].[UserId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]        FROM   [dbo].[UserRole] AS [Extent1]        INNER JOIN [dbo].[Roles] AS [Extent2] ON [Extent1].[RoleId] = [Extent2].[Id]        LEFT OUTER JOIN  (SELECT [Extent3].[UserId] AS [UserId], [Extent3].[RoleId] AS [RoleId], [Extent4].[Id] AS [Id], [Extent4].[Name] AS [Name], [Extent4].[Age] AS [Age], [Extent4].[Address] AS [Address], [Extent4].[DateAdded] AS [DateAdded]            FROM  [dbo].[UserRole] AS [Extent3]            INNER JOIN [dbo].[Users] AS [Extent4] ON [Extent4].[Id] = [Extent3].[UserId] ) AS [Join2] ON [Extent2].[Id] = [Join2].[RoleId]        WHERE ([Extent1].[UserId] = @EntityKeyValue1) AND ([Extent2].[DateAdded] > (SysDateTime()))    )  AS [Project1]    ORDER BY [Project1].[UserId] ASC, [Project1].[RoleId] ASC, [Project1].[Id] ASC, [Project1].[C1] ASC

看见这一坨的代码就心烦,而且这只是两段 SQL 代码的一个,因为上面我们使用:context.Users.FirstOrDefault(),也会生成一坨 SQL 代码,只不过没那么复杂而已,其实复杂之处,就是我们使用 Include 方式,把 User、Role 和 UserRole 表关联起来使用了,其实我们只是想获取某个 user 下的 Role 集合而已,在 stackoverflow 中有人也有同样的问题:,当然讲的比我详细多了。

其实最后的解决方式有点“无语”,为什么呢?看一下代码就知道了:

using (var context = new UserRoleDbContext()){    var user = context.Users        .Where(u => u.Id == 1)        .FirstOrDefault();    user.Roles = context.Entry(user)        .Collection(u => u.Roles)        .Query()        .Where(r => r.DateAdded > DateTime.Now)        .ToList();    foreach (var role in user.Roles)    {        Console.WriteLine(role.DateAdded);    }}

你可能发现了与上面代码的不同,就是我们使用 Entry 获取集合对象,重新给 user.Roles 属性赋值,因为 ToList 了,同样会产生两条 SQL 代码,但这种代码,我们是可以接受的:

SELECT     [Extent2].[Id] AS [Id],     [Extent2].[Name] AS [Name],     [Extent2].[DateAdded] AS [DateAdded]    FROM  [dbo].[UserRole] AS [Extent1]    INNER JOIN [dbo].[Roles] AS [Extent2] ON [Extent1].[RoleId] = [Extent2].[Id]    WHERE ([Extent1].[UserId] = @EntityKeyValue1) AND ([Extent2].[DateAdded] > (SysDateTime()))

示例 Demo 下载:

非常珍贵的参考资料:

转载地址:http://pudol.baihongyu.com/

你可能感兴趣的文章
研究人员发现:基于文本的AI模型容易受到改述攻击
查看>>
物联网技术周报第 103 期: DIY 智能音箱:基于 Raspberry Pi + Snowboy + AVS
查看>>
Creating Great Teams作者问答
查看>>
Azure编配器简化有状态无服务器工作流的创建
查看>>
AWS App Mesh:用于Envoy的服务网格控制平面
查看>>
专访ThoughtWorks王磊:从单块架构到微服务架构
查看>>
JetBrains大力推广Kotlin为哪般?
查看>>
IBM首家发布了公有云中的裸机Kubernetes
查看>>
火掌柜iOS端基于CocoaPods的组件二进制化实践
查看>>
Zabbix Agent端配置文件说明
查看>>
2.10环境变量PATH;2.11cp命令;2.12mv命令;2.13文档查看cat_more...
查看>>
mysql使用索引优化查询效率
查看>>
Salt Syndic配置
查看>>
Linux下Git和GitHub使用方法总结 (码云)
查看>>
《iOS应用开发》——1.3节到处看看
查看>>
C1X 系列 : 多线程 (N1494)
查看>>
《奇点来临》——手机也更智能
查看>>
阿里互联网研发团队的持续交付实践
查看>>
《重构HTML:改善Web应用的设计(修订版)》——第1章 重构1.1 为何重构
查看>>
科学音频处理(一):怎样使用 Octave 对音频文件进行读写操作
查看>>