Many-to-many relationships in EF Core 2.0 – Part 2: Hiding as IEnumerable
In the previous post we looked at how many-to-many relationships can be mapped using a join entity. In this post we’ll make the navigation properties to the join entity private so that they don’t appear in the public surface of our entity types. We’ll then add public IEnumerable properties that expose the relationship for reading without reference to the join entity.
Updating the model
In the first post our entity types that look like this:
public class Post
{
public int PostId { get; set; }
public string Title { get; set; } public ICollection<PostTag> PostTags { get; } = new List<PostTag>();
} public class Tag
{
public int TagId { get; set; }
public string Text { get; set; } public ICollection<PostTag> PostTags { get; } = new List<PostTag>();
}
But really we want our entity types to look more like this:
public class Post
{
public int PostId { get; set; }
public string Title { get; set; } public ICollection<Tag> Tags { get; } = new List<Tag>();
} public class Tag
{
public int TagId { get; set; }
public string Text { get; set; } public ICollection<Post> Posts { get; } = new List<Post>();
}
One way to do this is to make the PostTags navigation properties private and add public IEnumerable projections for their contents. For example:
public class Post
{
public int PostId { get; set; }
public string Title { get; set; } private ICollection<PostTag> PostTags { get; } = new List<PostTag>(); [NotMapped]
public IEnumerable<Tag> Tags => PostTags.Select(e => e.Tag);
} public class Tag
{
public int TagId { get; set; }
public string Text { get; set; } private ICollection<PostTag> PostTags { get; } = new List<PostTag>(); [NotMapped]
public IEnumerable<Post> Posts => PostTags.Select(e => e.Post);
}
Configuring the relationship
Making the navigation properties private presents a few problems. First, EF Core doesn’t pick up private navigations by convention, so they need to be explicitly configured:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PostTag>()
.HasKey(t => new { t.PostId, t.TagId }); modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Post)
.WithMany("PostTags"); modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Tag)
.WithMany("PostTags");
}
Using Include
Next, the Include call can no longer easily access to the private properties using an expression, so we use the string-based API instead:
var posts = context.Posts
.Include("PostTags.Tag")
.ToList();
Notice here that we can’t just Include tags like this:
var posts = context.Posts
.Include(e => e.Tags) // Won't work
.ToList();
This is because EF has no knowledge of “Tags”–it is not mapped. EF only knows about the private PostTags navigation property. This is one of the limitations I called out in Part 1. It would currently require messing with EF internals to be able to use Tags directly in queries.
Using the projected navigation properties
Reading the many-to-many relationship can now use the public properties directly. For example:
foreach (var tag in post.Tags)
{
Console.WriteLine($"Tag {tag.Text}");
}
But if we want to add and remove Tags we still need to do it using the PostTag join entity. We will address this in Part 3, but for now we can add a simple helper that gets PostTags by Reflection. Updating our test application to use this we get:
public class Program
{
public static void Main()
{
using (var context = new MyContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated(); var tags = new[]
{
new Tag { Text = "Golden" },
new Tag { Text = "Pineapple" },
new Tag { Text = "Girlscout" },
new Tag { Text = "Cookies" }
}; var posts = new[]
{
new Post { Title = "Best Boutiques on the Eastside" },
new Post { Title = "Avoiding over-priced Hipster joints" },
new Post { Title = "Where to buy Mars Bars" }
}; context.AddRange(
new PostTag { Post = posts[], Tag = tags[] },
new PostTag { Post = posts[], Tag = tags[] },
new PostTag { Post = posts[], Tag = tags[] },
new PostTag { Post = posts[], Tag = tags[] },
new PostTag { Post = posts[], Tag = tags[] },
new PostTag { Post = posts[], Tag = tags[] },
new PostTag { Post = posts[], Tag = tags[] },
new PostTag { Post = posts[], Tag = tags[] }); context.SaveChanges();
} using (var context = new MyContext())
{
var posts = LoadAndDisplayPosts(context, "as added"); posts.Add(context.Add(new Post { Title = "Going to Red Robin" }).Entity); var newTag1 = new Tag { Text = "Sweet" };
var newTag2 = new Tag { Text = "Buzz" }; foreach (var post in posts)
{
var oldPostTag = GetPostTags(post).FirstOrDefault(e => e.Tag.Text == "Pineapple");
if (oldPostTag != null)
{
GetPostTags(post).Remove(oldPostTag);
GetPostTags(post).Add(new PostTag { Post = post, Tag = newTag1 });
}
GetPostTags(post).Add(new PostTag { Post = post, Tag = newTag2 });
} context.SaveChanges();
} using (var context = new MyContext())
{
LoadAndDisplayPosts(context, "after manipulation");
}
} private static List<Post> LoadAndDisplayPosts(MyContext context, string message)
{
Console.WriteLine($"Dumping posts {message}:"); var posts = context.Posts
.Include("PostTags.Tag")
.ToList(); foreach (var post in posts)
{
Console.WriteLine($" Post {post.Title}");
foreach (var tag in post.Tags)
{
Console.WriteLine($" Tag {tag.Text}");
}
} Console.WriteLine(); return posts;
} private static ICollection<PostTag> GetPostTags(object entity)
=> (ICollection<PostTag>)entity
.GetType()
.GetRuntimeProperties()
.Single(e => e.Name == "PostTags")
.GetValue(entity);
}
This test code will be simplified significantly in the next post where we show how to make the projected navigations updatable.
Many-to-many relationships in EF Core 2.0 – Part 2: Hiding as IEnumerable的更多相关文章
- Many-to-many relationships in EF Core 2.0 – Part 3: Hiding as ICollection
In the previous post we ended up with entities that hide the join entity from the public surface. Ho ...
- Many-to-many relationships in EF Core 2.0 – Part 1: The basics
转载这个系列的文章,主要是因为EF Core 2.0在映射数据库的多对多关系时,并不像老的EntityFramework那样有原生的方法进行支持,希望微软在以后EF Core的版本中加入原生支持多对多 ...
- Many-to-many relationships in EF Core 2.0 – Part 4: A more general abstraction
In the last few posts we saw how to hide use of the join entity from two entities with a many-to-man ...
- EF Core 1.0 和 SQLServer 2008 分页的问题
EF Core 1.0 在sqlserver2008分页的时候需要指定用数字分页. EF Core1.0 生成的分页语句中使用了 Featch Next.这个语句只有在SqlServer2012的时候 ...
- ASP.NET Core 开发-Entity Framework (EF) Core 1.0 Database First
ASP.NET Core 开发-Entity Framework Core 1.0 Database First,ASP.NET Core 1.0 EF Core操作数据库. Entity Frame ...
- EF Core 1.0中使用Include的小技巧
(此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:由于EF Core暂时不支持Lazy Loading,所以利用Include来加载额外 ...
- .NET Core 1.0、ASP.NET Core 1.0和EF Core 1.0简介
.NET Core 1.0.ASP.NET Core 1.0和EF Core 1.0简介 英文原文:Reintroducing .NET Core 1.0, ASP.NET Core 1.0, and ...
- EF Core 2.0 新特性
前言 目前 EF Core 的最新版本为 2.0.0-priview1-final,所以本篇文章主要是针对此版本的一些说明. 注意:如果你要在Visual Studio 中使用 .NET Core 2 ...
- EF Core 2.0使用MsSql/Mysql实现DB First和Code First
参考地址 EF官网 ASP.NET Core MVC 和 EF Core - 教程系列 环境 Visual Studio 2017 最新版本的.NET Core 2.0 SDK 最新版本的 Windo ...
随机推荐
- child_process
child_process const { spawn } = require('child_process'); const ls = spawn('ls', ['-lh', '/usr']); l ...
- Hibernate 注解(Annotations 一)
1. @Entity 将一个类声明为一个实体bean(即一个持久化POJO类). 2. @Table 声明了该实体bean映射指定的表(table),目录(catalog)和schema名字 3. @ ...
- JS对象原型的理解
基于原型的语言 JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板.从原型继承方法和属性.原型对象 ...
- PowerDesigner设置所有int主键自增脚本
'*****************************************************************************dim model 'current mod ...
- window下Jekyll建站过程
> 前言 最近决定要写一个博客,先后注册了博客园和CSND的博客,但是他们的界面主题都不是很符合自己的要求,还没有足够个性化的发挥空间,遂决定自己建一个博客. 网上找了一下教程,感觉都不太详细, ...
- webstorm添加自定义代码块
widnow下使用alt+ctrl+s 调出setting面板 mac下使用command+,(逗号)调出Preferences面板 搜索live template选中js,在javascrpt 模板 ...
- maven学习(七)后续扩展、资料
写这几篇博客的来源是 "maven实战 + 网上的博客 + 平时使用的心得 ".记录的都是比较常用的东西,也有一些只做了大概了解.或者干脆直接略过,在这里做一下总结,如果有需要在进 ...
- “云中论道”之——使用开源技术和Azure公有云服务快速搭建云端IoT解决方案(上)
“云中论道”技术课堂第一课开讲啦!微软各路技术咖们齐聚一堂,为大家带来干货不断!作为“云中论道“课堂的开课之作,我们首先邀请到了微软Azure专家级的架构师:槐长清,他为我们带来了关于“使用开源技术和 ...
- windows远程桌面无法粘贴复制的解决方法
案例一:未勾选剪贴板,进入 远程桌面连接选项“本地资源->本地设备和资源->剪贴板”中,勾选剪贴板. 案例二:已勾选剪贴板,但是无法粘贴复制,进入远程服务器,关闭[rdpclip.exe] ...
- 初看Mybatis 源码 (三) SQL是怎么执行的
前面说到Java动态代理,Mybatis通过这种方式实现了我们通过getMapper方式得到的Dao接口,可以直接通过接口的没有实现的方法来执行sql. AuthUserDao mapper = se ...