实体框架更新多对多关系:虚拟与否

时间:2012-01-15 12:32:24

标签: c# entity-framework

我一年以来一直在使用EF4(不是代码优先),所以我真的不是它的专家。 我对使用保存n更新的多对多关系表示怀疑。

我在stackoverflow上找到了某个地方(我再也找不到网址)一个解决方案 - 更新现有的多对多关系 - 是不要声明“虚拟”属性;但是,如果我这样做,引擎无法通过轻松加载来加载数据。

你能解释一下原因吗? Otherwire,你能帮我找一些关于这个主题的很酷的文档吗?

THX

3 个答案:

答案 0 :(得分:65)

您可以通过这种方式更新多对多关系(作为向用户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();
}

如果User.Roles集合声明为virtual,则行user.Roles.Add(role);确实会触发延迟加载,这意味着用户的所有角色首先从添加新角色之前的数据库。

这实际上令人不安,因为您不需要加载整个Roles集合来向用户添加新角色。

但这并不意味着您必须删除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();
}

修改

如果要更新用户的整个角色集合,我更愿意使用预先加载(= 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();
}

您可以附加它们,而不是从数据库中加载新角色,因为您在示例中知道了键属性值。您还可以删除完全缺少的角色,而不是清除整个集合,而不是重新添加现有角色:

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();
}

答案 1 :(得分:1)

Slaumas的回答确实很好,但是我想补充一下如何在不先从数据库中加载对象的情况下插入多对多关系。如果您知道要连接的ID,则多余的数据库调用是多余的。关键是使用Attach()

有关附加的更多信息:

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);
}

答案 2 :(得分:0)

我正在使用db-first方法和自动映射器在模型和实体之间进行映射(MVC 5),并且还使用了急切的加载。

在我的情况下,有设备,并且可以有多个用户作为设备操作员:

    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();
        }
    }