Many-to-many relationships in EF Core 2.0 – Part 1: The basics
转载这个系列的文章,主要是因为EF Core 2.0在映射数据库的多对多关系时,并不像老的EntityFramework那样有原生的方法进行支持,希望微软在以后EF Core的版本中加入原生支持多对多关系的Fluent API,这样这个系列的文章就不需要看了~
As of EF Core 2.0, many-to-many relationships without an explicitly mapped join table are not supported. However, all is not lost. In this series of posts I will show:
- Mapping many-to-many relationships with a join entity/table
- Abstracting/hiding the join entity
- In a simple way for read-only access to the relationship
- In a more involved way that allows entities to be added and removed from each end
Limitations
Before going any further I want to be clear about two limitations with the approach used in all these posts:
- EF Core doesn’t know about un-mapped properties. This means that queries must still be written in terms of the join entity.
- The join entity is not gone; it’s still in application code and still mapped. It’s just that normal interactions with the model, such as those in your application, do not use it.
Addressing these limitations requires changes to the internals of EF Core. I don’t expect the limitations to go away until at least some parts of GitHub issue 1368 are implemented.
The model
A good example of a many-to-many relationship is blogs and tags. Every blog can have many tags, and every tag can be associated with many blogs. A typical way to model this is:
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>();
}
Modeling with a join entity
This cannot be mapped directly using foreign keys–each post would need multiple FK values for each tag, and vice-versa. Instead, another entity type is needed to bridge the gap and hold all the FK pairs. In a relational database this is often called a “join table”, and we will map it to a join entity:
public class PostTag
{
public int PostId { get; set; }
public Post Post { get; set; } public int TagId { get; set; }
public Tag Tag { get; set; }
}
This entity type has two FKs each associated with a navigation property one pointing to one side of the relationship (Post) and the other to the other side of the relationship (Tag). When we want to associate a Post with a Tag, we create a new PostTag instance and set the navigation properties to point to the Post and the Tag. Equivalently, we could set the PostId FK to the PK value of the Post and the TagId FK to the PK value to the Tag.
To use this join entity we need to update the original entity types to map through it:
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>();
}
Configuring the join entity type
The join entity type needs to have a key defined since there is no key that EF can figure out by convention. Since pairs of PostId and TagId values are unique, we can use these pairs as a composite key for the entity:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PostTag>()
.HasKey(t => new { t.PostId, t.TagId });
}
The actual relationships don’t need to be configured explicitly in this case because they can be figured out by convention.
Using the many-to-many relationship
Let’s write a little console application to show this working. First, we need a DbContext:
public class MyContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0"); protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PostTag>()
.HasKey(t => new { t.PostId, t.TagId });
}
}
I’m using SQL Server LocalDb as a database provider, but this code should work the same with any database provider.
Next, here is a little test application that manipulates the many-to-many relationship in various ways:
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 = post.PostTags.FirstOrDefault(e => e.Tag.Text == "Pineapple");
if (oldPostTag != null)
{
post.PostTags.Remove(oldPostTag);
post.PostTags.Add(new PostTag { Post = post, Tag = newTag1 });
}
post.PostTags.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(e => e.PostTags)
.ThenInclude(e => e.Tag)
.ToList(); foreach (var post in posts)
{
Console.WriteLine($" Post {post.Title}");
foreach (var tag in post.PostTags.Select(e => e.Tag))
{
Console.WriteLine($" Tag {tag.Text}");
}
} Console.WriteLine(); return posts;
}
}
Summarizing this application, it:
- Deletes any stale test database and creates a new one
- Creates some Posts and some Tags and then creates join entities to associate them
- Saves all the Posts and Tags and their relationships to the database
- Loads the entities in a new context and displays them
- Manipulates the relationships:
- A new Post is created and tracked
- Every “Pineapple” Tag is removed and replaced with a “Sweet” Tag
- Every Post gets a new “Buzz” Tag
- These are again saved, re-read, and displayed.
On my machine, this results in the following output:
Dumping posts as added:
Post Best Boutiques on the Eastside
Tag Golden
Tag Pineapple
Post Avoiding over-priced Hipster joints
Tag Girlscout
Tag Cookies
Post Where to buy Mars Bars
Tag Golden
Tag Pineapple
Tag Girlscout
Tag Cookies Dumping posts after manipulation:
Post Best Boutiques on the Eastside
Tag Golden
Tag Sweet
Tag Buzz
Post Avoiding over-priced Hipster joints
Tag Girlscout
Tag Cookies
Tag Buzz
Post Where to buy Mars Bars
Tag Golden
Tag Girlscout
Tag Cookies
Tag Sweet
Tag Buzz
Post Going to Red Robin
Tag Buzz Press any key to continue . . .
So that’s simple many-to-many relationship with a join entity. In the next post we’ll start hiding aspects of the join entity.
Many-to-many relationships in EF Core 2.0 – Part 1: The basics的更多相关文章
- 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 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 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 ...
随机推荐
- Spring课程 Spring入门篇 6-2 ProxyFactoryBean及相关内容(上)
1 解析 1.1 类的方式实现各种通知需要实现的接口 1.2 创建Spring aop代理的优点及方法 1.3 代理控制切入点和通知的顺序的代码实现(具体完全实现,见代码2.1) 1.4 代理方式选择 ...
- mysql三表联合查询
-- SELECT d.userId, d.userPhoNum, a.orderId, a.productType, b.courseId, b.courseName, c.payJe -- FRO ...
- php浮点数加减乘除bug
项目测试阶段,少部分微信支付成功,但是在异步通知校对订单金额是否一致时,一直被认定订单金额不一致. 类似于: 浏览器输出: 分析: 因为计算机二进制无法准确表示部分浮点数(如2.03.0.58等等), ...
- JS判断是否到达页面底部
<script type="text/javascript">//判断整个文档到底部$(window).scroll(function(){ //滚动条所在位置的 ...
- eclipse 内存溢出
2011年02月22日 星期二 11:14 eclipse.exe -vmargs -Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M ec ...
- CentOS 7运维管理笔记(8)----Apache基于域名的虚拟主机配置
使用基于域名的虚拟主机配置是比较流行的方式,可以在同一个IP上配置多个域名并且都通过80端口访问. (1) 在网卡 eth0的第五个接口上配置 192.168.1.215 这个地址: (2) 配置/e ...
- 03_ActiveMQ安全机制
[ActiveMQ安全机制] [ ActiveMQ的web管理界面 ] 地址 http://127.0.0.1:8161/admin ActiveMQ管理控制台使用jetty部署,所以需要修改密码, ...
- JavaScript中模块化工具require.js
什么是require.js? RequireJS是一个非常小巧的JavaScript模块载入框架,是AMD规范最好的实现者之一.它还同时可以和其他的框架协同工作,使用RequireJS必将使您的前端代 ...
- .net CombinedGeometry的合并模式
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="h ...
- react-native 在Xcode上传到iTunes Connect里报错
在xcode里面点击“upload to app store”的时候,提示“the session's status is FAILED and the error description is 'C ...