2009-07-25 37 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個月的時間內查看此代碼時,您會再次感到疑惑。