Эффективный способ проверки, существует ли в EF4.1 отношение многие ко многим
У меня есть отношения многие ко многим между двумя объектами - Media и MediaCollection. Я хочу проверить, существует ли определенный носитель в коллекции. Я могу сделать это следующим образом:
mediaCollection.Media.Any(m => m.id == mediaId)
Тем не менее, mediaCollection.Media является ICollection, поэтому для меня это выглядит так, как будто для получения этой проверки придется извлекать все медиафайлы в коллекции из базы данных. Поскольку в коллекции может быть много медиа, это кажется очень неэффективным. Я подумал, что мне следует использовать метод IQueryable, но я не понимаю, как это сделать для отношений "многие ко многим".
Как я могу проверить наличие отношений без извлечения всей коллекции?
РЕДАКТИРОВАТЬ
Я генерирую модель данных EF из своей базы данных, а затем использую встроенные шаблоны VS POCO T4 для генерации контекста данных и классов сущностей. Я думаю, что проблема в том, что сгенерированный код не возвращает EntityCollection для свойств навигации, а вместо этого ObjectSet. ObjectSet реализует IQueryable, но не предоставляет метод CreateSourceQuery().
Вот урезанная версия соответствующих строк из контекста:
public partial class Entities : ObjectContext
{
public const string ConnectionString = "name=Entities";
public const string ContainerName = "Entities";
#region Constructors
public Entities()
: base(ConnectionString, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
public Entities(string connectionString)
: base(connectionString, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
public Entities(EntityConnection connection)
: base(connection, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
#endregion
#region ObjectSet Properties
public ObjectSet<MediaCollection> MediaCollections
{
get { return _mediaCollections ?? (_mediaCollections = CreateObjectSet<MediaCollection>("MediaCollections")); }
}
private ObjectSet<MediaCollection> _mediaCollections;
// snipped many more
#endregion
}
А вот урезанная версия класса для объекта MediaCollection:
public partial class MediaCollection
{
#region Primitive Properties
// snipped
#endregion
#region Navigation Properties
public virtual ICollection<Medium> Media
{
get
{
if (_media == null)
{
var newCollection = new FixupCollection<Medium>();
newCollection.CollectionChanged += FixupMedia;
_media = newCollection;
}
return _media;
}
set
{
if (!ReferenceEquals(_media, value))
{
var previousValue = _media as FixupCollection<Medium>;
if (previousValue != null)
{
previousValue.CollectionChanged -= FixupMedia;
}
_media = value;
var newValue = value as FixupCollection<Medium>;
if (newValue != null)
{
newValue.CollectionChanged += FixupMedia;
}
}
}
}
private ICollection<Medium> _media;
private void FixupMedia(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Medium item in e.NewItems)
{
if (!item.MediaCollections.Contains(this))
{
item.MediaCollections.Add(this);
}
}
}
if (e.OldItems != null)
{
foreach (Medium item in e.OldItems)
{
if (item.MediaCollections.Contains(this))
{
item.MediaCollections.Remove(this);
}
}
}
}
// snip
#endregion
}
И, наконец, вот коллекция FixupCollection, которую также генерирует шаблон:
public class FixupCollection<T> : ObservableCollection<T>
{
protected override void ClearItems()
{
new List<T>(this).ForEach(t => Remove(t));
}
protected override void InsertItem(int index, T item)
{
if (!this.Contains(item))
{
base.InsertItem(index, item);
}
}
}
3 ответа
Таким образом, кажется, что встроенный шаблон VS POCO T4 не генерирует ничего, эквивалентного CreateSourceQuery(). Независимо от того! Мы можем кодировать это сами. Если вы добавите следующий код в файл.tt контекста и выполните регенерацию:
public ObjectQuery<T> CreateNavigationSourceQuery<T>(object entity, string navigationProperty)
{
var ose = ObjectStateManager.GetObjectStateEntry(entity);
var rm = ObjectStateManager.GetRelationshipManager(entity);
var entityType = (System.Data.Metadata.Edm.EntityType)ose.EntitySet.ElementType;
var navigation = entityType.NavigationProperties[navigationProperty];
var relatedEnd = rm.GetRelatedEnd(navigation.RelationshipType.FullName, navigation.ToEndMember.Name);
return ((dynamic)relatedEnd).CreateSourceQuery();
}
тогда мы можем проверить существование "многие ко многим" следующим образом:
var exists = _context.CreateNavigationSourceQuery<Medium>(mediaCollection, "Media")
.Any(m => m.Id == medium.Id);
Перейдем к ответу Роуэн об использовании CreateSourceQuery в CTP4-коде. Сначала для этого.
Вы можете сделать это, но вам нужен контекст для этого:
bool exists = context.Entry(mediaCollection)
.Collection(m => m.Media)
.Query()
.Any(x => x.Id == mediaId);
Редактировать:
Если вы используете ObjectContext API с прокси-серверами POCO вместо DbContext API, предыдущий пример не будет работать. Вы можете попробовать это:
context.ContextOptions.LazyLoadingEnabled = false;
bool exists = ((EntityCollection<Media>)mediaCollection.Media).CreateSourceQuery()
.Any(x => x.Id == mediaId);
context.ContextOptions.LazyLoadingEnabled = true;
Пытаться,
mediaCollection.CreateSourceQuery()
.Any(....
CreateSourceQuery создаст IQueryable для ассоциации.