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的更多相关文章

  1. 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 ...

  2. Many-to-many relationships in EF Core 2.0 – Part 1: The basics

    转载这个系列的文章,主要是因为EF Core 2.0在映射数据库的多对多关系时,并不像老的EntityFramework那样有原生的方法进行支持,希望微软在以后EF Core的版本中加入原生支持多对多 ...

  3. 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 ...

  4. EF Core 1.0 和 SQLServer 2008 分页的问题

    EF Core 1.0 在sqlserver2008分页的时候需要指定用数字分页. EF Core1.0 生成的分页语句中使用了 Featch Next.这个语句只有在SqlServer2012的时候 ...

  5. 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 ...

  6. EF Core 1.0中使用Include的小技巧

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:由于EF Core暂时不支持Lazy Loading,所以利用Include来加载额外 ...

  7. .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 ...

  8. EF Core 2.0 新特性

    前言 目前 EF Core 的最新版本为 2.0.0-priview1-final,所以本篇文章主要是针对此版本的一些说明. 注意:如果你要在Visual Studio 中使用 .NET Core 2 ...

  9. 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 ...

随机推荐

  1. JDK自带工具keytool生成ssl证书 和 HTTPS双向认证

    创建证书(第一步) keytool -genkey -alias "baidu" -keypass "123456" -keystore "D:/ba ...

  2. VC++ IPv6的支持

    最近根据项目需要,要在产品中添加对IpV6的支持,因此研究了一下IPV6的相关内容,Ipv6 与原来最直观的改变就是地址结构的改变,IP地址由原来的32位扩展为128,这样原来的地址结构肯定就不够用了 ...

  3. COGS2294 释迦

    传送门 就是传说中的任意模数卷积嘛……有三模数NTT和拆系数FFT等做法,我比较懒不想动脑子,就用了三模数NTT的做法…… 卷积之后每个数可以达到$10^{23}$左右的级别,直接long doubl ...

  4. Docker常用操作指令

    1.查看正在运行的容器 docker ps 2.查看所有容器 docker ps -a 3.停用所有正在运行的容器 docker stop $(docker ps -q) 4.删除所有容器 docke ...

  5. 项目经验:GIS<MapWinGIS>建模第四天

    实现了查询,与定位功能

  6. android中的内部存储与外部存储

    我们先来考虑这样一个问题: 打开手机设置,选择应用管理,选择任意一个App,然后你会看到两个按钮,一个是清除缓存,另一个是清除数据,那么当我们点击清除缓存的时候清除的是哪里的数据?当我们点击清除数据的 ...

  7. 千里之堤毁于蚁穴(慎用HD Wallets)

    转自:http://blog.sina.com.cn/s/blog_12ce70a430102vbu9.html 千里之堤毁于蚁穴(慎用HD Wallets) -- 随机系列谈之四 现在我们都该明白, ...

  8. Linux 内核超时导致虚拟机无法正常启动

    问题描述 当 Linux 虚拟机启动时,通过串口输出或者启动日志, 观察到超时的报错.导致虚拟机无法正常启动和连接. 问题分析 常见的超时报错范例如下: 复制 INFO: task swapper:1 ...

  9. SQL Server ->> FIRST_VALUE和LAST_VALUE函数

    两个都是SQL SERVER 2012引入的函数.用于返回在以分组和排序后取得最后一行的某个字段的值.很简单两个函数.ORDER BY字句是必须的,PARITION BY则是可选. 似乎没什么好说的. ...

  10. 如何阅读 Redis 源码?ZZ

    原文链接 在这篇文章中, 我将向大家介绍一种我认为比较合理的 Redis 源码阅读顺序, 希望可以给对 Redis 有兴趣并打算阅读 Redis 源码的朋友带来一点帮助. 第 1 步:阅读数据结构实现 ...