我发现EFCore处理多对多关系的方式非常乏味,特别是更新连接的集合。这是一个频繁的需求:视图模型从前端传入了一个新的嵌套实体列表,我必须为每个嵌套实体编写一个方法,以确定需要删除什么,需要添加什么,然后进行删除和添加操作。有时一个实体有多个多对多关系,我必须为每个集合写出几乎相同的代码。
我认为可以使用通用方法来避免重复工作,但我很难想出具体实现方法。
让我先展示一下我目前的做法。
假设我们有以下模型:
使用流畅API定义的密钥
我认为可以使用通用方法来避免重复工作,但我很难想出具体实现方法。
让我先展示一下我目前的做法。
假设我们有以下模型:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<PersonCar> PersonCars { get; set; } = new List<PersonCar>();
}
public class Car
{
public int Id { get; set; }
public string Manufacturer { get; set; }
public virtual ICollection<PersonCar> PersonCars { get; set; } = new List<PersonCar>();
}
public class PersonCar
{
public virtual Person Person { get; set; }
public int PersonId { get; set; }
public virtual Car Car { get; set; }
public int CarId { get; set; }
}
使用流畅API定义的密钥
modelBuilder.Entity<PersonCar>().HasKey(t => new { t.PersonId, t.CarId });
我们添加一个新的人和相关车辆列表:
var person = new Person
{
Name = "John",
PersonCars = new List<PersonCar>
{
new PersonCar { CarId = 1 },
new PersonCar { CarId = 2 },
new PersonCar { CarId = 3 }
}
};
db.Persons.Add(person);
db.SaveChanges();
John拥有汽车1,2,3
。John在前端更新他的汽车,因此现在我获得了一个新的汽车id列表,所以我会像这样更新(实际代码可能会使用模型,并且可能会调用类似于这样的方法):
public static void UpdateCars(int personId, int[] newCars)
{
using (var db = new PersonCarDbContext())
{
var person = db.Persons.Include(x => x.PersonCars).ThenInclude(x => x.Car).Single(x => x.Id == personId);
var toRemove = person.PersonCars.Where(x => !newCars.Contains(x.CarId)).ToList();
var toAdd = newCars.Where(c => !person.PersonCars.Any(x => x.CarId == c)).ToList();
foreach (var pc in toRemove)
{
person.PersonCars.Remove(pc);
}
foreach (var carId in toAdd)
{
var pc = db.PersonCars.Add(new PersonCar { CarId = carId, PersonId = person.Id });
}
db.SaveChanges();
}
}
我会先筛选需要删除的对象和需要添加的对象,然后执行相应的操作。这些步骤都非常简单,但在实际应用中,一个实体可能会有多个多对多的集合,例如标签、分类、选项等等。每个更新方法都基本相同,我最终会重复编写相同的代码好几次。例如,假设 Person 实体也有一个与 Category 实体的多对多关系,代码如下:
public static void UpdateCategory(int personId, int[] newCats)
{
using (var db = new PersonCarDbContext())
{
var person = db.Persons.Include(x => x.PersonCategories).ThenInclude(x => x.Category).Single(x => x.Id == personId);
var toRemove = person.PersonCategories.Where(x => !newCats.Contains(x.CategoryId)).ToList();
var toAdd = newCats.Where(c => !person.PersonCategories.Any(x => x.CategoryId == c)).ToList();
foreach (var pc in toRemove)
{
person.PersonCategories.Remove(pc);
}
foreach (var catId in toAdd)
{
var pc = db.PersonCategories.Add(new PersonCategory { CategoryId = catId, PersonId = person.Id });
}
db.SaveChanges();
}
}
这是完全相同的代码,只是引用了不同的类型和属性。我最终会得到大量重复的代码。我做错了吗?还是这是使用通用方法的好例子?
我觉得这是使用通用方法的好地方,但我不太清楚如何做到。
它将需要实体类型、连接实体类型和外部实体类型,因此可能类似于:
public T UpdateJoinedEntity<T, TJoin, Touter>(PersonCarDbContext db, int entityId, int[] nestedids)
{
//.. do same logic but with reflection?
}
该方法将运作并找出正确的属性,并进行必要的删除和添加。
这个可行吗?我不知道如何做到,但看起来似乎是可能的。