Эффективный способ проверки, существует ли в EF4.1 отношение многие ко многим - PullRequest
2 голосов
/ 12 июля 2011

У меня есть отношения многие ко многим между двумя объектами - Media и MediaCollection. Я хочу проверить, существует ли определенный носитель в коллекции. Я могу сделать это следующим образом:

mediaCollection.Media.Any(m => m.id == mediaId)

Тем не менее, mediaCollection.Media - это ICollection, поэтому для меня это выглядит так, как будто для получения этой проверки придется извлекать все медиафайлы в коллекции из базы данных. Поскольку в коллекции может быть много медиа, это кажется очень неэффективным. Я подумал, что мне следует использовать метод IQueryable, но я не понимаю, как это сделать для отношений «многие ко многим».

Как я могу проверить наличие отношений, не извлекая всю коллекцию?

EDIT

Я генерирую модель данных 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 ]

4 голосов
/ 12 июля 2011

Вы можете сделать это, но вам нужен контекст для этого:

bool exists = context.Entry(mediaCollection)
                     .Collection(m => m.Media)
                     .Query()
                     .Any(x => x.Id == mediaId);

Edit:

Если вы используете API ObjectContext с прокси POCO вместо DbContext API, предыдущий пример не будет работать. Вы можете попробовать это:

context.ContextOptions.LazyLoadingEnabled = false;
bool exists = ((EntityCollection<Media>)mediaCollection.Media).CreateSourceQuery()
                                                              .Any(x => x.Id == mediaId);
context.ContextOptions.LazyLoadingEnabled = true;
2 голосов
/ 18 июля 2011

Так что кажется, что встроенный шаблон 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 Code Сначала для этого.

1 голос
/ 12 июля 2011

Try,

 mediaCollection.CreateSourceQuery()
     .Any(....

CreateSourceQuery создаст IQueryable для ассоциации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...