Правильный способ удалить отношение многие ко многим через linq to sql? - PullRequest
3 голосов
/ 25 июля 2009

Допустим, у нас есть две таблицы с отношением «многие ко многим»:

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 ]

1 голос
/ 26 июля 2009

Вот «маленький» метод расширения, который я написал, чтобы упростить эту проблему:

  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, который ссылается на многие B. через AB.

  1. Получить все B из A.Bs через 'fkvalue'.
  2. Получите то, что было добавлено.
  3. Получите то, что было удалено.
  4. Добавьте все новые и создайте AB через 'fkmaker'.
  5. Удалите все удаленные.
  6. При желании удалите другие объекты, на которые есть ссылки, с помощью 'fkdelete'.

Я бы хотел улучшить это, используя вместо этого Expression, чтобы я мог лучше «шаблонировать» метод, но он бы работалто же самое.

0 голосов
/ 26 июля 2009

Лично я бы заменил

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

на

Db.LeftRights.DeleteAllOnSubmit(relation)

, потому что кажется более очевидным, что произойдет. Если вам интересно, как теперь работает «.Remove», вам будет интересно заново, когда вы посмотрите на этот код через 6 месяцев.

0 голосов
/ 26 июля 2009

Возьмите два, используя выражения:

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

Первое выражение теперь используется для установления «отношения». Оттуда я могу вывести 2 ранее необходимых делегата. Теперь не более:)

Важно:

Чтобы это работало правильно в Linq2Sql, вам нужно пометить ассоциации из промежуточной таблицы с помощью DeleteOnNull = "true"'в файле dbml. Это сломает конструктор, но все равно будет корректно работать с SqlMetal.

Чтобы разблокировать конструктор, вам нужно удалить эти дополнительные атрибуты.

...