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.

原文链接

最新文章

  1. 简单例子了解View的事件分发
  2. javascript创建对象的几种模式
  3. SQL SERVER 属性OWNER不可用于数据库xxx。该对象可能没有此属性,也可能是访问权限不足而无法检索。
  4. awk分隔符设置技巧
  5. UnicodeToGB2312
  6. JavaScrip之对象与继承
  7. register_shutdown_function 函数详解
  8. Mysql 基础3
  9. JS基础语法
  10. js调用MVC3自带js验证
  11. U-boot的环境变量: bootcmd 和bootargs
  12. dom 关键字提示
  13. TCP/IP协议原理与应用笔记22:静态和动态路由选择
  14. 推荐 15 个 Angular.js 应用扩展指令(参考应用)
  15. mongodb的连接问题,绑定IP惹的祸
  16. AJAX异步加载
  17. Asp.Net MVC学习总结(二)——控制器与动作(Controller And Action)
  18. C# 将数据表导出到Excel通用方法
  19. 网络七层OSI模型简介
  20. Spring Cloud 微服务

热门文章

  1. spring cglib 与 jdk 动态代理
  2. html+css 布局篇
  3. JavaScript总结摘要
  4. SQA和系统测试规程
  5. GitHub教程(二) 删除已有仓库
  6. sqlserver批量删除表
  7. Android中如何在Eclipse中关联源代码?(图文)
  8. BEM,SASS,LESS,bootstrap:如何有效地将这些方法,工具和框架聪明地整合?
  9. 爬虫入门之反反爬虫机制cookie UA与中间件(十三)
  10. Python学习---Python下[列表]的学习