Jos*_*h L 0 c# sql-server entity-framework entity-framework-core asp.net-core
我一直在研究以下关于如何使用 Entity Framework Core 创建多对多关系的教程: https: //learn.microsoft.com/en-us/ef/core/modeling/relationships。
我正在开发组管理功能,我的模型如下:
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>();
}
Run Code Online (Sandbox Code Playgroud)
在我的 dbContext 中,我定义了连接表来映射两个单独的一对多关系:
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);
}
Run Code Online (Sandbox Code Playgroud)
我需要的是创建一个导航属性来直接访问组的成员,而不必使用 .Include() 来包含 GroupMembers 连接表,然后使用第二个 .Include() 来包含 User 对象。
原因是 a) 客户端期望一个具有第一级用户对象数组属性的组对象,b) 我无法以 json 形式序列化返回的对象,因为它会导致 in self GroupMember 表的组属性的引用循环。
EF Core 现在还支持多对多关系
只需省略链接实体,EF 将在后台创建链接表。
例如
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>();
}
Run Code Online (Sandbox Code Playgroud)
在 EF core 的早期版本中,您必须使用 NotMapped 属性的解决方法。您仍然需要决定从序列化中排除哪些导航属性以避免循环。
一种想法是在实体上放置一个 NotMapped 属性,为我们提供跳级导航属性,然后忽略 JSON 序列化中真正的导航属性。另外,为了打破循环,还有一个 ContractResolver,它会跳过“导航属性”的序列化,以消除对象图中的循环。
像这样:
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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
输出
{
"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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
5723 次 |
| 最近记录: |