博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Many-to-many relationships in EF Core 2.0 – Part 2: Hiding as IEnumerable
阅读量:6441 次
发布时间:2019-06-23

本文共 6749 字,大约阅读时间需要 22 分钟。

In the 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
PostTags { get; } = new List
();}public class Tag{ public int TagId { get; set; } public string Text { get; set; } public ICollection
PostTags { get; } = new List
();}

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
Tags { get; } = new List
();}public class Tag{ public int TagId { get; set; } public string Text { get; set; } public ICollection
Posts { get; } = new List
();}

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
PostTags { get; } = new List
(); [NotMapped] public IEnumerable
Tags => PostTags.Select(e => e.Tag);}public class Tag{ public int TagId { get; set; } public string Text { get; set; } private ICollection
PostTags { get; } = new List
(); [NotMapped] public IEnumerable
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
() .HasKey(t => new { t.PostId, t.TagId }); modelBuilder.Entity
() .HasOne(pt => pt.Post) .WithMany("PostTags"); modelBuilder.Entity
() .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[0], Tag = tags[0] },                new PostTag { Post = posts[0], Tag = tags[1] },                new PostTag { Post = posts[1], Tag = tags[2] },                new PostTag { Post = posts[1], Tag = tags[3] },                new PostTag { Post = posts[2], Tag = tags[0] },                new PostTag { Post = posts[2], Tag = tags[1] },                new PostTag { Post = posts[2], Tag = tags[2] },                new PostTag { Post = posts[2], Tag = tags[3] });            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
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
GetPostTags(object entity) => (ICollection
)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.

 

 

转载地址:http://cmdwo.baihongyu.com/

你可能感兴趣的文章
求前缀表达式的值
查看>>
Dockers 部署 MongoDB + mongo-express
查看>>
php学习 5 无限级分类
查看>>
客户机页表遍历
查看>>
传统定时器
查看>>
JAVA抽象类和接口
查看>>
认识检查器
查看>>
HTML-在canvas画图中,图片的线上链接已配置允许跨域后,仍然出错提示跨域,怎么解决?...
查看>>
vs项目启动调试时,显示找不到文件问题
查看>>
Netty 4 内存管理:sun.misc.Cleaner
查看>>
dubbo之线程模型
查看>>
thinkphp分页带数据
查看>>
solr多条件查询(三)
查看>>
c++ 函数声明
查看>>
linux下,免密码登录
查看>>
街道管理
查看>>
hdu 3501 Calculation 2 (欧拉函数)
查看>>
csv2mysql
查看>>
可以免费下载视频素材和模板网站汇总
查看>>
生成包含数字和大小写字母的随机码
查看>>