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. However, it was not possible to add or removed entities through this public surface. To enable this we need an ICollection implementation that acts as a true facade over the real join entity collection and delegates all responsibilities to that collection.
The collection implementation
Here’s one possible implementation of such a collection:
public class JoinCollectionFacade<T, TJoin> : ICollection<T>
{
private readonly ICollection<TJoin> _collection;
private readonly Func<TJoin, T> _selector;
private readonly Func<T, TJoin> _creator; public JoinCollectionFacade(
ICollection<TJoin> collection,
Func<TJoin, T> selector,
Func<T, TJoin> creator)
{
_collection = collection;
_selector = selector;
_creator = creator;
} public IEnumerator<T> GetEnumerator()
=> _collection.Select(e => _selector(e)).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator(); public void Add(T item)
=> _collection.Add(_creator(item)); public void Clear()
=> _collection.Clear(); public bool Contains(T item)
=> _collection.Any(e => Equals(_selector(e), item)); public void CopyTo(T[] array, int arrayIndex)
=> this.ToList().CopyTo(array, arrayIndex); public bool Remove(T item)
=> _collection.Remove(
_collection.FirstOrDefault(e => Equals(_selector(e), item))); public int Count
=> _collection.Count; public bool IsReadOnly
=> _collection.IsReadOnly;
}
The idea is pretty simple–operations on the facade are translated into operations on the underlying collection. Where needed, a “selector” delegate is used to extract the desired target entity from the join entity. Likewise, a “creator” delegate creates a new join entity instance from the target entity when a new relationship is added.
实际上我觉得改成下面这样会更好,另外Remove和Contains方法我觉得没什么用,所以暂时就先放的抛出NotSupportedException异常:
public class ActionCollection<T, TJoin> : ICollection<T>
{
protected readonly Func<T, TJoin> creator;
protected readonly Func<TJoin, T> selector;
protected readonly Func<ICollection<TJoin>> collectionSelector; public ActionCollection(Func<ICollection<TJoin>> collectionSelector, Func<T, TJoin> creator, Func<TJoin, T> selector)
{
this.collectionSelector = collectionSelector;
this.creator = creator;
this.selector = selector;
} public int Count => collectionSelector().Count; public bool IsReadOnly => collectionSelector().IsReadOnly; public void Add(T item)
{
collectionSelector().Add(creator(item));
} public void Clear()
{
collectionSelector().Clear();
} public bool Contains(T item)
{
throw new NotSupportedException("Contains is not supported");
} public void CopyTo(T[] array, int arrayIndex)
{
List<T> list = new List<T>(); foreach (var tJoin in collectionSelector())
{
list.Add(selector(tJoin));
} list.CopyTo(array, arrayIndex);
} public IEnumerator<T> GetEnumerator()
{
return this.collectionSelector().Select(tj => this.selector(tj)).GetEnumerator();
} public bool Remove(T item)
{
throw new NotSupportedException("Remove is not supported");
} IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
Updating the model
We need to initialize instances of this collection in our entities:
public class Post
{
public Post()
=> Tags = new JoinCollectionFacade<Tag, PostTag>(
PostTags,
pt => pt.Tag,
t => new PostTag { Post = this, Tag = t }); public int PostId { get; set; }
public string Title { get; set; } private ICollection<PostTag> PostTags { get; } = new List<PostTag>(); [NotMapped]
public ICollection<Tag> Tags { get; }
} public class Tag
{
public Tag()
=> Posts = new JoinCollectionFacade<Post, PostTag>(
PostTags,
pt => pt.Post,
p => new PostTag { Post = p, Tag = this }); public int TagId { get; set; }
public string Text { get; set; } private ICollection<PostTag> PostTags { get; } = new List<PostTag>(); [NotMapped]
public ICollection<Post> Posts { get; }
}
Using the ICollection navigations
Notice how Tags and Posts are now ICollection properties instead of IEnumerable properties. This means we can add and remove entities from the many-to-many collections without using the join entity directly. Here’s the test application updated to show this:
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" }
}; posts[].Tags.Add(tags[]);
posts[].Tags.Add(tags[]);
posts[].Tags.Add(tags[]);
posts[].Tags.Add(tags[]);
posts[].Tags.Add(tags[]);
posts[].Tags.Add(tags[]);
posts[].Tags.Add(tags[]);
posts[].Tags.Add(tags[]); context.AddRange(tags);
context.AddRange(posts); 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 oldTag = post.Tags.FirstOrDefault(e => e.Text == "Pineapple");
if (oldTag != null)
{
post.Tags.Remove(oldTag);
post.Tags.Add(newTag1);
}
post.Tags.Add(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;
}
}
Notice that:
- When seeding the database, we add Tags directly to the Tags collection on Post.
- When finding and removing existing tags, we can search directly for the Tag and remove it from the Post.Tags collection without needing to use the join entity.
It’s worth calling out again that, just like in the previous post, we still can’t use Tags directly in any query. For example, using it for Include won’t work:
var posts = context.Posts
.Include(e => e.Tags) // Won't work
.ToList();
EF has no knowledge of “Tags”–it is not mapped. EF only knows about the private PostTags navigation property.
Functionally, this is about as far as we can go without starting to mess with the internals of EF. However, in one last post I’ll show how to abstract out the collection and join entity a bit more so that it is easier reuse for different types.
Many-to-many relationships in EF Core 2.0 – Part 3: Hiding as ICollection的更多相关文章
- 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. ...
- 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 ...
随机推荐
- 第2章、数据与简单计算程序(c语言入门)
一.写程序描述数据的处理 ①描述数据 ②描述过程 ps:和做鸡蛋饼差不多:数据是材料,作法是过程. 空白符的作用是使用程序更容易了解. 二.标识符 1.基本要求: ①由字母,数字,下划线组成. ②数字 ...
- NodeJS require路径
项目需要用nodejs,感觉nodejs是前端装逼神器了,是通向全栈工程师的必经之路哇,接下来开始踏上学习nodejs的征程.下面是第一个hello,world的程序. 1.server.js文件,这 ...
- makefile 模板 (template)
本文把makefile 分成了三份:生成可执行文件的makefile,生成静态链接库的makefile,生成动态链接库的makefile. 这些makefile都很简单,一般都是一看就会用,用法也很容 ...
- 【阿里云产品公测】云引擎ACE公测感受
听说阿里云ACE开始公测了,怀着激动的心情赶紧试用了一下. 这是我用ACE做出来的效果:http://haoyuming.aliapp.com/ 大家点点看看啊 A*W/Q<~I :eSwX ...
- lua 中的上n级模块路径函数分享
--- 得到 lua 模块路径的上 n 级,返回的的字符串结尾有个 "." 字符 -- @usage getUpFloder("math.base.core", ...
- linux 文件常用操作
linux 文件基本操作 新建文件:touch test 不会替换重名文件,并且linux一切都是文件,文件夹和文件不能重名 新建文件夹:mkdir test使用 -p 参数,同时创建父目录(如果不存 ...
- Hyper-V复制
Hyper-V复制: 默认HV01上的所有虚机都被复制到HV02的 Hyper-V Replica 目录下.虚机启用复制后,当需要启用虚机副本时,要先在HV01上把原虚机关机,然后在HV02上选择故障 ...
- ACM HDU-2952 Counting Sheep
Counting Sheep Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) T ...
- .net 流(Stream) - 文件流、内存流、网络流
转自:http://www.oseye.net/user/kevin/blog/85 一.文件流 FileStream FileStream流继承与Stream类,一个FileStream类的实例实际 ...
- easyui学习笔记2—在行内进行表格的增删改操作
第一篇笔记中记录了如何实现表格的增删改,那个是点击之后跳出来一个对话框然后进行的,这里是在表格本身上进行的操作,也很简单,但是这里发现一个版本问题,也可以说是兼容性问题. 1.首先我们看引用的js和c ...