Many to many в LINQ to SQL

Thursday, 19 June 08, 20:21 Z

В LINQ to SQL отсутствует понятие отношения many-to-many. Например, для таблиц Book, Author и Book2Author в БД мы получим не 2 класса, что было бы логично, а 3 и класс Books не будет содержать свойство типа EntitySet<Author>, отображающего many-to-many связь между Book и Author. Но у нас есть возможность добавить такое свойство самостоятельно.

Создаем новый класс ManyToManyList<Target, T>, который реализует интерфейс IList<T>:

    public class ManyToManyList<Target, T> : IList<T>
    {
    }

Для доступа к данным, которые определяет реализуемое many to many отношение, создаем поле типа IQueryable<T>:

    public class ManyToManyList<Target, T> : IList<T>
    {
        IQueryable<T> _query;
    }

Реализуем некоторые методы IList<T>, не изменяющие данные:

    public class ManyToManyList<Target, T> : IList<T>
    {
        IQueryable<T> _query;

        IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _query.GetEnumerator();
        }

        IEnumerator<T> System.Collections.Generic.IEnumerable<T>.GetEnumerator()
        {
            return _query.GetEnumerator();
        }

        #region ICollection<T>

        public bool Contains(T item)
        {
            return _query.Contains(item);
        }

        #endregion

        ...
    }

Для изменения промежуточных таблиц в БД, обеспечивающих реализацию many to many отношения, при добавлении T в Target или удалении T из Target создаем 2 делегата Action<T> в классе:

    public class ManyToManyList<Target, T> : IList<T>
    {
        Action<T> _onAdd;
        Action<T> _onRemove;

        ...
    }

Реализуем методы, отвечающие за добавление и удаление записей, в которых будут вызываться соответствующие делегаты

    public class ManyToManyList<Target, T> : IList<T>
    {
        Action<T> _onAdd;
        Action<T> _onRemove;

        ...

        #region ICollection<T>

        public void Add(T item)
        {
            if (_onAdd != null) _onAdd(item);
        }

        public bool Remove(T item)
        {
            if (_onRemove != null)
            {
                _onRemove(item);
                return true;
            }
            return false;
        }

        public void Clear()
        {
            if (_onRemove != null)
            {
                foreach (var item in _query)
                {
                    _onRemove(item);
                }
            }
        }

        #endregion

        ...
    }

Добавляем конструкторы:

    public class ManyToManyList<Target, T> : IList<T>
    {
        public ManyToManyList(Target target, IQueryable<T> query)
        {
            this._target = target;
            this._query = query;
        }

        public ManyToManyList(Target target, IQueryable<T> query,
            Action<T> onAdd, Action<T> onRemove) : this(target, query)
        {
            this._onAdd = onAdd;
            this._onRemove = onRemove;
        }

        ...
    }

Пример использования класса ManyToManyList<Target, T> для классов Book и Author:

    public partial class Book
    {
        ManyToManyList<Book, Author> _authors;

        public ManyToManyList<Book, Author> Authors
        {
            get
            {
                if (_authors == null)
                {
                    IQueryable<Author> query = DB.Book2Author.Where(
                        x => x.BookId == this.Id).Select(x => x.Author);
                    _authors = new ManyToManyList<Book, Author>(
                        this, query, OnAddAuthor, OnRemoveAuthor);
                }
                return _authors;
            }
        }

        void OnAddAuthor(Author author)
        {
            Book2Author b2a = new Book2Author();
            b2a.AuthorId = author.Id;
            Book2Author.Add(b2a);
        }

        void OnRemoveAuthor(Author author)
        {
            Book2Author b2a = this.Book2Author.First(
                x => x.AuthorId == author.Id);
            DB.Book2Authors.DeleteOnSubmit(b2a);
        }

        ...
    }