2009-07-25 80 views
3

比方说,我们有两个表有许多一对多的关系:通过linq to sql删除多对多关系的正确方法?

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

或者我必须在两个零件上都做?这里需要什么?

回答

1

这里是一个“小”扩展方法我写来简化这个问题:

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是多对多关系p,其中AB是中间表。

这会给你:

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

你现在有一个的对象,通过AB,提及许多B的。

  1. 通过'fkvalue'从A.Bs获取所有B.
  2. 获取添加的内容。
  3. 获取已删除的内容。
  4. 添加所有新的,并通过'fkmaker'构造AB。
  5. 删除所有删除的。
  6. (可选)通过'fkdelete'删除其他引用的对象。

我想通过使用Expression来改善这一点,所以我可以更好地“模板”方法,但它的工作原理是一样的。

+0

太好了。你也许可以提供算法的描述? – Will 2009-07-25 20:32:07

+0

哦,和XML评论总是很好! – Will 2009-07-25 20:35:53

0

取二,使用表达式:

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正常工作,你需要“DeleteOnNull =‘真’”在DBML文件,以纪念从中间表的关联。这将打破设计师,但仍然与SqlMetal正常工作。

要解除设计师的破解,您需要删除这些附加属性。

0

就个人而言,我会更换

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

Db.LeftRights.DeleteAllOnSubmit(relation) 

,因为它似乎更明显了什么事情发生。如果您想知道“.Remove”现在是什么样的行为,那么在6个月的时间内查看此代码时,您会再次感到疑惑。