在EF Core多对多关系中创建直接导航属性 [英] Create Direct Navigation Property in EF Core Many to Many Relationship

查看:239
本文介绍了在EF Core多对多关系中创建直接导航属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在研究以下有关如何使用Entity Framework Core创建多对多关系的教程:解决方案

在EF6中,您只需省略链接实体,EF就会在后台创建链接表.

例如

 公共类组{public int GroupId {get;放;}公用字符串GroupName {get;放;}公共虚拟列表< User>GroupMembers {get;放;} = new List< User>();}公共类用户{public int UserId {get;放;}公用字符串电子邮件{get;放;}公共虚拟列表< Group>MemberOf {get;set;} = new List< Group>();} 

但是,这在EF Core上不起作用.因此,让我们四处逛逛并建立解决方法.

一个想法是将NotMapped属性放在为我们提供跳过级导航属性的实体上,然后忽略JSON序列化中的实际导航属性.为了打破循环,还有一个ContractResolver,它将跳过导航属性"的序列化以消除对象图中的循环.

赞:

 使用Microsoft.EntityFrameworkCore;使用Microsoft.EntityFrameworkCore.Design;使用Newtonsoft.Json;使用系统;使用System.Linq;使用System.Collections.Generic;使用System.ComponentModel.DataAnnotations.Schema;使用System.ComponentModel.DataAnnotations;使用Newtonsoft.Json.Serialization;使用System.Reflection;命名空间EFCore2Test{公共班组{public int GroupId {get;放;}公用字符串GroupName {get;放;}[JsonIgnore]公共虚拟ICollection< GroupMember>GroupMembers {get;} = new HashSet< GroupMember>();[未映射]公共IList< User>用户=>GroupMembers.Select(m => m.User).ToList();}公共类GroupMember{public int GroupId {get;放;}公众团体团体{get;放;}public int UserId {get;放;}公共用户用户{放;}}公共类用户{public int UserId {get;放;}公用字符串电子邮件{get;放;}[JsonIgnore]公共虚拟ICollection< GroupMember>MemberOf {get;} = new HashSet< GroupMember>();[未映射]公共IList< Group>组=>MemberOf.Select(m => m.Group).ToList();}公共类Db:DbContext{公共DbSet< User>用户{get;放;}公共DbSet< Group>群组{get;放;}公共DbSet< GroupMember>GroupMembers {get;放;}受保护的重写void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.Entity< GroupMember>().HasKey(t => new {t.UserId,t.GroupId});modelBuilder.Entity< GroupMember>().HasOne(pt => pt.User).WithMany(p => p.MemberOf).HasForeignKey(pt => pt.UserId);modelBuilder.Entity< GroupMember>().HasOne(pt => pt.Group).WithMany(t => t.GroupMembers).HasForeignKey(pt => pt.GroupId);}受保护的重写void OnConfiguring(DbContextOptionsBuilder optionsBuilder){optionsBuilder.UseSqlServer("Server =(local); Database = Test; Trusted_Connection = True; MultipleActiveResultSets = true");base.OnConfiguring(optionsBuilder);}}班级计划{公共类DontSerialze< T>:DefaultContractResolver{受保护的重写JsonProperty CreateProperty(MemberInfo成员,MemberSerialization memberSerialization){JsonProperty属性= base.CreateProperty(member,memberSerialization);如果(property.PropertyType == typeof(T)){property.ShouldSerialize = o =>错误的;}归还财产;}}静态void Main(string [] args){使用(var db = new Db()){db.Database.EnsureDeleted();db.Database.EnsureCreated();var users = Enumerable.Range(1,20).Select(i => new User(){Email = $"user {i} @wherever"}).ToList();var groups = Enumerable.Range(1,5).Select(i => new Group(){GroupName = $"group {i}"}).ToList();var userGroups =(从g中的用户中的u中的u选择新的GroupMember(){User = u,Group = g}).OrderBy(gm =>(gm.Group.GroupName + gm.User.Email).GetHashCode())拿(100).ToList();db.Users.AddRange(users);db.Groups.AddRange(groups);db.GroupMembers.AddRange(userGroups);db.SaveChanges();var ser = new JsonSerializer();ser.Formatting = Formatting.Indented;ser.ContractResolver = new DontSerialze< IList< User>>();foreach(用户中的var u.Take(2)){ser.Serialize(Console.Out,u);Console.WriteLine();}}Console.WriteLine(点击任意键退出");Console.ReadKey();}}} 

输出

  {"UserId":20,电子邮件":"user1 @ wherever",组":[{"GroupId":4"GroupName":"group1"},{"GroupId":2"GroupName":"group3"},{"GroupId":5"GroupName":"group4"},{"GroupId":1"GroupName":"group5"},{"GroupId":3,"GroupName":"group2"}]}{"UserId":18,电子邮件":"user2 @ wherever",组":[{"GroupId":2"GroupName":"group3"},{"GroupId":1"GroupName":"group5"},{"GroupId":5"GroupName":"group4"},{"GroupId":3,"GroupName":"group2"},{"GroupId":4"GroupName":"group1"}]}按任意键退出 

I've been working through the following tutorial on how to create a many-to-many relationship using Entity Framework Core: https://docs.microsoft.com/en-us/ef/core/modeling/relationships.

I'm working on a group management feature and my models are the following:

public class Group
{
    public int GroupId { get; set; }
    public string GroupName { get; set;}            
    public virtual List<GroupMember> GroupMembers { get; set; } = new List<GroupMember>();
}

public class GroupMember
{
    public int GroupId { get; set; }
    public Group Group { get; set; }

    public int UserId { get; set; }
    public User User{ get; set; } 
}

public class User
{
    public int UserId { get; set; }
    public string Email { get; set; }
    public List<GroupMember> MemberOf {get; set;} = new List<GroupMember>();
}

And in my dbContext I have defined my join table for mapping two separate one-to-many relationships:

public DbSet<Group> Groups { get; set; }   
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{ 
                modelBuilder.Entity<GroupMember>()
                    .HasKey(t => new { t.UserId, t.GroupId });

                modelBuilder.Entity<GroupMember>()
                    .HasOne(pt => pt.User)
                    .WithMany(p => p.MemberOf)
                    .HasForeignKey(pt => pt.UserId);

                modelBuilder.Entity<GroupMember>()
                    .HasOne(pt => pt.Group)
                    .WithMany(t => t.GroupMembers)
                    .HasForeignKey(pt => pt.GroupId);
}

What I need is to create a navigation property to access a group's members directly rather than having to use a .Include() to include the GroupMembers join table, followed by a second .Include() to include the User objects.

The reason for this is that a) the client side is expecting a group object with a property for an array of user objects at the first level and b) I am unable to serialize the returned object in json because it is resulting in an in self referencing loops for the group property of the GroupMember table.

解决方案

In EF6 you would just omit the Linking Entity and EF will create the linking table behind the scenes.

eg

public class Group
{
    public int GroupId { get; set; }
    public string GroupName { get; set;}            
    public virtual List<User> GroupMembers { get; set; } = new List<User>();
}
public class User
{
    public int UserId { get; set; }
    public string Email { get; set; }
    public virtual List<Group> MemberOf {get; set;} = new List<Group>();
}

But, that doesn't work on EF Core. So let's go around to the workshop and build a workaround.

One idea is to put a NotMapped property on the entities that gives us the skip-level navigation property, and then ignore the real Navigation Properties in JSON serialization. Also to break the cycles there's a ContractResolver that will skip serialization of the "navigation property" to eliminate cycles in the object graph.

Like this:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json.Serialization;
using System.Reflection;

namespace EFCore2Test
{
    public class Group
    {
        public int GroupId { get; set; }
        public string GroupName { get; set; }
        [JsonIgnore]
        public virtual ICollection<GroupMember> GroupMembers { get; } = new HashSet<GroupMember>();

        [NotMapped]
        public IList<User> Users => GroupMembers.Select(m => m.User).ToList();
    }

    public class GroupMember
    {
        public int GroupId { get; set; }
        public Group Group { get; set; }
        public int UserId { get; set; }
        public User User { get; set; }
    }

    public class User
    {

        public int UserId { get; set; }
        public string Email { get; set; }

        [JsonIgnore]
        public virtual ICollection<GroupMember> MemberOf { get; } = new HashSet<GroupMember>();

        [NotMapped]
        public IList<Group> Groups => MemberOf.Select(m => m.Group).ToList();
    }

    public class Db : DbContext
    {


        public DbSet<User> Users { get; set; }
        public DbSet<Group> Groups { get; set; }

        public DbSet<GroupMember> GroupMembers { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<GroupMember>()
                .HasKey(t => new { t.UserId, t.GroupId });

            modelBuilder.Entity<GroupMember>()
                .HasOne(pt => pt.User)
                .WithMany(p => p.MemberOf)
                .HasForeignKey(pt => pt.UserId);

            modelBuilder.Entity<GroupMember>()
                .HasOne(pt => pt.Group)
                .WithMany(t => t.GroupMembers)
                .HasForeignKey(pt => pt.GroupId);
        }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer("Server=(local);Database=Test;Trusted_Connection=True;MultipleActiveResultSets=true");
            base.OnConfiguring(optionsBuilder);
        }
    }


    class Program
    {

        public class DontSerialze<T> : DefaultContractResolver
        {


            protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
            {

                JsonProperty property = base.CreateProperty(member, memberSerialization);

                if (property.PropertyType == typeof(T))
                {
                    property.ShouldSerialize = o => false;
                }

                return property;
            }
        }
        static void Main(string[] args)
        {

            using (var db = new Db())
            {
                db.Database.EnsureDeleted();
                db.Database.EnsureCreated();

                var users = Enumerable.Range(1, 20).Select(i => new User() { Email = $"user{i}@wherever" }).ToList();

                var groups = Enumerable.Range(1, 5).Select(i => new Group() { GroupName = $"group{i}" }).ToList();

                var userGroups = (from u in users from g in groups select new GroupMember() { User = u, Group = g })
                                 .OrderBy(gm => (gm.Group.GroupName + gm.User.Email).GetHashCode())
                                 .Take(100)
                                 .ToList();

                db.Users.AddRange(users);
                db.Groups.AddRange(groups);
                db.GroupMembers.AddRange(userGroups);

                db.SaveChanges();

                var ser = new JsonSerializer();
                ser.Formatting = Formatting.Indented;
                ser.ContractResolver = new DontSerialze<IList<User>>();

                foreach (var u in users.Take(2))
                {
                    ser.Serialize(Console.Out, u);
                    Console.WriteLine();
                }

            }
            Console.WriteLine("Hit any key to exit");
            Console.ReadKey();
        }
    }
}

outputs

{
  "UserId": 20,
  "Email": "user1@wherever",
  "Groups": [
    {
      "GroupId": 4,
      "GroupName": "group1"
    },
    {
      "GroupId": 2,
      "GroupName": "group3"
    },
    {
      "GroupId": 5,
      "GroupName": "group4"
    },
    {
      "GroupId": 1,
      "GroupName": "group5"
    },
    {
      "GroupId": 3,
      "GroupName": "group2"
    }
  ]
}
{
  "UserId": 18,
  "Email": "user2@wherever",
  "Groups": [
    {
      "GroupId": 2,
      "GroupName": "group3"
    },
    {
      "GroupId": 1,
      "GroupName": "group5"
    },
    {
      "GroupId": 5,
      "GroupName": "group4"
    },
    {
      "GroupId": 3,
      "GroupName": "group2"
    },
    {
      "GroupId": 4,
      "GroupName": "group1"
    }
  ]
}
Hit any key to exit

这篇关于在EF Core多对多关系中创建直接导航属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆