C# 实体框架更新多对多关系:虚拟与否
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8869632/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
entity framework update many to many relationship: virtual or not
提问by frabiacca
I've been using EF4 (not code-first) since a year, so I'm not really an expert with it. I've a doubt in using many-to-many relationship regarding save n update.
一年以来我一直在使用 EF4(不是代码优先),所以我并不是真正的专家。我对使用关于保存 n 更新的多对多关系有疑问。
I read somewhere on stackoverflow (i can't find the url anymore) that one solution - to update an existing many-to-many relation - is to not declare "virtual" property; but, if i do this way, the engine can't load dataas with easy loading.
我在 stackoverflow 上的某个地方(我再也找不到 url 了)读到一个解决方案 - 更新现有的多对多关系 - 是不声明“虚拟”属性;但是,如果我这样做,引擎将无法轻松加载数据。
Can you pls explain me the reason? Otherwire, could you please help me in finding some cool docs on this theme?
你能解释一下原因吗?Otherwire,你能帮我找到一些关于这个主题的很酷的文档吗?
thx
谢谢
采纳答案by Slauma
You can update a many-to-many relationship this way (as an example which gives user 3 the role 5):
您可以通过这种方式更新多对多关系(例如,用户 3 的角色为 5):
using (var context = new MyObjectContext())
{
var user = context.Users.Single(u => u.UserId == 3);
var role = context.Roles.Single(r => r.RoleId == 5);
user.Roles.Add(role);
context.SaveChanges();
}
If the User.Rolescollection is declared as virtualthe line user.Roles.Add(role);will indeed trigger lazy loading which means that allroles for the user are loaded first from the database before you add the new role.
如果User.Roles集合被声明为virtual行user.Roles.Add(role);确实会触发延迟加载,这意味着在添加新角色之前,首先从数据库加载用户的所有角色。
This is in fact disturbing because you don't need to load the whole Rolescollection to add a new role to the user.
这实际上令人不安,因为您无需加载整个Roles集合即可向用户添加新角色。
But this doesn't mean that you have to remove the virtualkeyword and abandon lazy loading altogether. You can just turn off lazy loading in this specific situation:
但这并不意味着您必须删除virtual关键字并完全放弃延迟加载。在这种特定情况下,您可以关闭延迟加载:
using (var context = new MyObjectContext())
{
context.ContextOptions.LazyLoadingEnabled = false;
var user = context.Users.Single(u => u.UserId == 3);
var role = context.Roles.Single(r => r.RoleId == 5);
user.Roles = new List<Role>(); // necessary, if you are using POCOs
user.Roles.Add(role);
context.SaveChanges();
}
Edit
编辑
If you want to update the whole roles collection of a user I would prefer to load the original roles with eager loading ( = Include). You need this list anyway to possibly remove some roles, so you don't need to wait until lazy loading fetches them from the database:
如果您想更新用户的整个角色集合,我更愿意使用预先加载( = Include)加载原始角色。无论如何,您都需要此列表来删除某些角色,因此您无需等到延迟加载从数据库中获取它们:
var newRolsIds = new List<int> { 1, 2, 5 };
using (var context = new MyObjectContext())
{
var user = context.Users.Include("Roles")
.Single(u => u.UserId == 3);
// loads user with roles, for example role 3 and 5
var newRoles = context.Roles
.Where(r => newRolsIds.Contains(r.RoleId))
.ToList();
user.Roles.Clear();
foreach (var newRole in newRoles)
user.Roles.Add(newRole);
context.SaveChanges();
}
Instead of loading the new roles from the database you can also attach them since you know in the example the key property value. You can also remove exactly the missing roles instead of clearing the whole collection and instead of re-adding the exisiting roles:
除了从数据库加载新角色之外,您还可以附加它们,因为您在示例中知道键属性值。您还可以完全删除丢失的角色,而不是清除整个集合,而不是重新添加现有的角色:
var newRolsIds = new List<int> { 1, 2, 5 };
using (var context = new MyObjectContext())
{
var user = context.Users.Include("Roles")
.Single(u => u.UserId == 3);
// loads user with roles, for example role 3 and 5
foreach (var role in user.Roles.ToList())
{
// Remove the roles which are not in the list of new roles
if (!newRoleIds.Contains(role.RoleId))
user.Roles.Remove(role);
// Removes role 3 in the example
}
foreach (var newRoleId in newRoleIds)
{
// Add the roles which are not in the list of user's roles
if (!user.Roles.Any(r => r.RoleId == newRoleId))
{
var newRole = new Role { RoleId = newRoleId };
context.Roles.Attach(newRole);
user.Roles.Add(newRole);
}
// Adds roles 1 and 2 in the example
}
// The roles which the user was already in (role 5 in the example)
// have neither been removed nor added.
context.SaveChanges();
}
回答by Ogglas
Slaumas answer is really good but I would like to add how you can insert a many to many relationship without loading objects from database first. If you know the Ids to connect that extra database call is redundant. The key is to use Attach().
Slaumas 的回答非常好,但我想补充一下如何插入多对多关系而不先从数据库加载对象。如果您知道连接该额外数据库调用的 ID,则是多余的。关键是用Attach()。
More info about Attach:
关于附加的更多信息:
https://stackoverflow.com/a/3920217/3850405
https://stackoverflow.com/a/3920217/3850405
public class ConnectBToADto
{
public Guid AId { get; set; }
public Guid BId { get; set; }
}
public void ConnectBToA(ConnectBToADto dto)
{
var b = new B() { Id = dto.BId };
Context.B.Attach(b);
//Add a new A if the relation does not exist. Redundant if you now that both AId and BId exists
var a = Context.A.SingleOrDefault(x => x.Id == dto.AId);
if(a == null)
{
a = new A() { Id = dto.AId };
Context.A.Add(a);
}
b.As.Add(a);
}
回答by Bengü Hasdil
I am using db-first approach and automapper to map between model and entity (MVC 5) and also using eager loading.
我正在使用 db-first 方法和 automapper 在模型和实体(MVC 5)之间进行映射,并使用预先加载。
In my scenario, there are equipments and there can be multiple users as equipment operators:
在我的场景中,有设备并且可以有多个用户作为设备操作员:
public void Create()
{
using (var context = new INOBASEEntities())
{
// first i need to map model 'came from the view' to entity
var _ent = (Equipment)Mapper.Map(this, typeof(EquipmentModel), typeof(Equipment));
context.Entry(_ent).State = System.Data.Entity.EntityState.Added;
// I use multiselect list on the view for operators, so i have just ids of users, i get the operator entity from user table and add them to equipment entity
foreach(var id in OperatorIds)
{
AspNetUsersExtended _usr = context.AspNetUsersExtended.Where(u => u.Id == id).FirstOrDefault();
// this is operator collection
_ent.AspNetUsersExtended2.Add(_usr);
}
context.SaveChanges();
Id = _ent.Id;
}
}
public void Update()
{
using (var context = new INOBASEEntities())
{
var _ent = (Equipment)Mapper.Map(this, typeof(EquipmentModel), typeof(Equipment));
context.Entry(_ent).State = System.Data.Entity.EntityState.Modified;
var parent = context.Equipment
.Include(x => x.AspNetUsersExtended2)//include operators
.Where(x => x.Id == Id).FirstOrDefault();
parent.AspNetUsersExtended2.Clear(); // get the parent and clear child collection
foreach (var id in OperatorIds)
{
AspNetUsersExtended _usr = context.AspNetUsersExtended.Where(u => u.Id == id).FirstOrDefault();
parent.AspNetUsersExtended2.Add(_usr);
}
// this line add operator list to parent entity, and also update equipment entity
context.SaveChanges();
}
}

