代码之家  ›  专栏  ›  技术社区  ›  Will

通过LINQ to SQL删除多对多关系的正确方法?

  •  3
  • Will  · 技术社区  · 15 年前

    假设我们有两个具有多对多关系的表:

    public class Left{ /**/ }
    
    public class Right{ /**/ }
    
    public class LeftRight{ /**/ }
    

    以下内容是否足以取消挂起这些记录(忽略多个关系或未定义关系的可能性)?

    public void Unhook(Left left, Right right){
      var relation = from x in Left.LeftRights where x.Right == right;
      left.LeftRrights.Remove(relation.First());
      Db.SubmitChanges();
    }
    

    还是我必须在这两个部分都做?这里需要什么?

    3 回复  |  直到 15 年前
        1
  •  1
  •   leppie    15 年前

    下面是我为简化这个问题而编写的“小”扩展方法:

      public static class EntitySetExtensions
      {
        public static void UpdateReferences<FK, FKV>(
            this EntitySet<FK> refs,
            Func<FK, FKV> fkvalue,
            Func<FKV, FK> fkmaker,
            Action<FK> fkdelete,
            IEnumerable<FKV> values)
          where FK : class
          where FKV : class
        {
          var fks = refs.Select(fkvalue).ToList();
          var added = values.Except(fks);
          var removed = fks.Except(values);
    
          foreach (var add in added)
          {
            refs.Add(fkmaker(add));
          }
    
          foreach (var r in removed)
          {
            var res = refs.Single(x => fkvalue(x) == r);
            refs.Remove(res);
            fkdelete(res);
          }
        }
      }
    

    可能会有所改进,但这对我很有帮助:)

    例子:

    Left entity = ...;
    IEnumerable<Right> rights = ...;
    
    entity.LeftRights.UpdateReferences(
     x => x.Right, // gets the value
     x => new LeftRight { Right = x }, // make reference
     x => { x.Right = null; }, // clear references
     rights);
    

    算法说明:

    假设A和B是多对多关系,其中AB是中间表。

    这将给您:

    class A { EntitySet<B> Bs {get;} }
    class B { EntitySet<A> As {get;} }
    class AB { B B {get;} A A {get;} }
    

    你现在有一个a的对象,它通过a b引用了很多b。

    1. 通过“fkvalue”从a.bs获取所有b。
    2. 获取添加的内容。
    3. 获取删除的内容。
    4. 添加所有新的,并通过“fkmaker”构造ab。
    5. 删除所有删除的。
    6. 或者,通过“fkdelete”删除其他引用对象。

    我想通过使用表达式来改进这一点,这样我可以更好地“模板化”该方法,但它的工作原理是一样的。

        2
  •  0
  •   leppie    15 年前

    取二,使用表达式:

    public static class EntitySetExtensions
    {
      public static void UpdateReferences<FK, FKV>(
          this EntitySet<FK> refs,
          Expression<Func<FK, FKV>> fkexpr,
          IEnumerable<FKV> values)
        where FK : class
        where FKV : class
      {
        Func<FK, FKV> fkvalue = fkexpr.Compile();
        var fkmaker = MakeMaker(fkexpr);
        var fkdelete = MakeDeleter(fkexpr);
    
        var fks = refs.Select(fkvalue).ToList();
        var added = values.Except(fks);
        var removed = fks.Except(values);
    
        foreach (var add in added)
        {
          refs.Add(fkmaker(add));
        }
    
        foreach (var r in removed)
        {
          var res = refs.Single(x => fkvalue(x) == r);
          refs.Remove(res);
          fkdelete(res);
        }
      }
    
      static Func<FKV, FK> MakeMaker<FKV, FK>(Expression<Func<FK, FKV>> fkexpr)
      {
        var me = fkexpr.Body as MemberExpression;
    
        var par = Expression.Parameter(typeof(FKV), "fkv");
        var maker = Expression.Lambda(
            Expression.MemberInit(Expression.New(typeof(FK)), 
              Expression.Bind(me.Member, par)), par);
    
        var cmaker = maker.Compile() as Func<FKV, FK>;
        return cmaker;
      }
    
      static Action<FK> MakeDeleter<FK, FKV>(Expression<Func<FK, FKV>> fkexpr)
      {
        var me = fkexpr.Body as MemberExpression;
        var pi = me.Member as PropertyInfo;
    
        var par = Expression.Parameter(typeof(FK), "fk");
        var maker = Expression.Lambda(
            Expression.Call(par, pi.GetSetMethod(), 
              Expression.Convert(Expression.Constant(null), typeof(FKV))), par);
    
        var cmaker = maker.Compile() as Action<FK>;
        return cmaker;
      }
    }
    

    现在使用起来非常简单!:)

    Left entity = ...;
    IEnumerable<Right> rights = ...;
    
    entity.LeftRights.UpdateReferences(x => x.Right, rights);
    

    第一个表达式现在用于建立“关系”。从这里我可以推断出之前需要的两个代表。现在没有了:)

    重要:

    要使它在linq2sql中正常工作,需要用DBML文件中的“deleteonnull=”true“标记中介表中的关联。这将破坏设计器,但仍能正确地使用sqlmetal。

    要断开设计器的连接,需要删除这些附加属性。

        3
  •  0
  •   Dave Roberts    15 年前

    就我个人而言,我会取代

    left.LeftRrights.Remove(relation.First());
    

    具有

    Db.LeftRights.DeleteAllOnSubmit(relation)
    

    因为接下来会发生什么似乎更为明显。如果您想知道“.remove”现在的行为是什么,那么当您在6个月内查看此代码时,您将再次感到疑惑。