转载这个系列的文章,主要是因为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的更多相关文章

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

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

  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. html和css入门 (三)

    文本样式 color 文本颜色 属性名 color 属性值 预定义的颜色名 | 十六进制值的颜色值 | RGB颜色值 默认值 依赖用户代理 描述 设置文本的颜色 direction 文本方向( 属性名 ...

  2. 关于display:inline-block布局导致错位问题分析

    移动端设计稿需求是这样的,如下图: 未知的几个头像从左至右并行居中排列. 一般可能直接使用float,但是设计图要求头像排列始终是居中的,于是想到要让它们成为行内元素,然后可使用的方法有flex bo ...

  3. Aysnc-callback with future in distributed system

    Aysnc-callback with future in distributed system

  4. YoLo 实践(1)

    目录 YoLo 实践(1) 目标: 实施方法: Step 0. 测试项目是否可以正常运行 运行效果图 使用VOC数据结构训练模型 Step1: 生成统一格式的标注文件和类别文件 Step2: 加载预训 ...

  5. 升级CocoaPod遇到ERROR: While executing gem ... (TypeError) no implicit conversion of nil into String问题的解决方法

    如下图: 先执行命令: gem update --system 再升级: sudo gem install cocoapods --pre 这样就能够正常升级了.

  6. Linux 下载百度网盘大文件

    Linux 下没有百度网盘客户端,用浏览器下载速度慢得急死人 鼠标移到链接处, 右键, 然后复制链接 接着在终端里输入 axel -n 50 -o filename.ext “粘贴链接到此处” axe ...

  7. notepad ++ 编辑 powershell profile 文件时的诡异问题

    使用notepad 编辑 C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1 时,记事本打开和用notepad++打开显示的内容居然不一样. ...

  8. 无缓冲和带缓冲channel的区别

    常规定义的channel都是默认不带缓冲的,如下代码所示 package main import ( "fmt" ) func main() { c := make(chan in ...

  9. ZT Abstract Factory

    实际上,Abstract Factory 模式是为创建一组(有多类)相关或依赖的对象提供创建接口,而 Factory 模式正如我在相应的文档中分析的是为一类对象提供创建接口或延迟对象的创建到子类中实现 ...

  10. RequireJS进阶-模块的优化及配置的详解

    概述 关于RequireJS已经有很多文章介绍过了.这个工具可以将你的JavaScript代码轻易的分割成苦干个模块(module)并且保持你的代码模块化与易维护性.这样,你将获得一些具有互相依赖关系 ...