Вы можете создать IManyToManySet<TEntity>
интерфейс, который может быть возвращен из вашего свойства many to many и иметь реализацию ManyToManySet<TSource, TCross, TDestination>
с функциями вставки и удаления запроса.
Интерфейс может выглядеть следующим образом:
public interface IManyToManySet<TEntity> : IEnumerable<TEntity>
where TEntity : class
{
int Count { get; }
void Add(TEntity entity);
bool Remove(TEntity entity);
void AddRange(IEnumerable<TEntity> collection);
}
И реализация может выглядеть следующим образом:
public class ManyToManySet<TSource, TCross, TDestination>
: IManyToManySet<TDestination>, IEnumerable<TDestination>
where TDestination : class
where TSource : class
where TCross : class
{
private TSource source;
private EntitySet<TCross> crossSet;
private Func<TCross, TDestination> destinationSelector;
private Func<TSource, TDestination, TCross> crossFactory;
public ManyToManySet(TSource source,
EntitySet<TCross> crossSet,
Func<TCross, TDestination> destinationSelector,
Func<TSource, TDestination, TCross> crossFactory)
{
this.source = source;
this.crossSet = crossSet;
this.destinationSelector = destinationSelector;
this.crossFactory = crossFactory;
}
public int Count
{
get { return this.crossSet.Count; }
}
public void Add(TDestination entity)
{
var newEntity = this.crossFactory(this.source, entity);
this.crossSet.Add(newEntity);
}
public bool Remove(TDestination entity)
{
var existingEntity = (
from c in this.crossSet
where this.destinationSelector(c) == entity
select c)
.SingleOrDefault();
if (existingEntity != null)
{
return this.crossSet.Remove(existingEntity);
}
return false;
}
public void AddRange(IEnumerable<TDestination> collection)
{
foreach (var entity in collection)
{
this.Add(entity);
}
}
public IEnumerator<TDestination> GetEnumerator()
{
return this.crossSet.Select(this.destinationSelector)
.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
В этой реализации необходимо указать несколько вещей:
-
TSource
экземпляр, который указывает на сущность, которая определяет свойство. EntitySet<TCross>
, который указывает на список сущностей, которые определяют перекрестную таблицу. - Функция проекции, позволяющая преобразовыватьот
TCross
до TDestination
. - Заводская функция, позволяющая создавать новые
TCross
на основе TSource
и TDestination
.
Translatingэто на практическом примере (с использованием Product
и Order
) даст вам следующее свойство в сущности Order
:
private IManyToManySet<Product> products;
public IManyToManySet<Product> Products
{
get
{
if (this.products != null)
{
this.products = new ManyToManySet<Order, OrderProduct, Product>(
this, this.OrderProducts, op => op.Product,
(o, p) => new OrderProduct { Order = o, Product = p });
}
return this.products;
}
}
и следующее свойство в сущности Product
:
private IManyToManySet<Order> orders;
public IManyToManySet<Order> Orders
{
get
{
if (this.orders == null)
{
this.orders = new ManyToManySet<Product, OrderProduct, Order>(
this, this.OrderProducts, op => op.Order,
(p, o) => new OrderProduct { Order = o, Product = p });
}
return this.orders;
}
}
Интерфейс IManyToManySet<T>
фактическиdundant, потому что вы можете вернуть ManyToMany<TSource, TCross, TDestination>
напрямую.Однако интерфейс скрывает аргументы типа TSource
и TCross
, что делает его немного более читаемым для пользователя этого свойства.
Обратите внимание, что эта реализация имеет такое же поведение при загрузке, что и LINQ to SQL EntitySet<T>
;Когда он используется, он загружает полный набор объектов в память.Как и в случае EntitySet<T>
с использованием where
или First
в коллекции, все еще загружается полная коллекция из базы данных.Вы должны знать об этом.
Важное отличие состоит в том, что LINQ to SQL понимает EntitySet<T>
свойства в запросах LINQ.Наличие IManyToManySet<T>
внутри запроса LINQ с треском провалится.
Надеюсь, это поможет.