【问题标题】:Efficient way of checking if many-to-many relationship exists in EF4.1检查EF4.1中是否存在多对多关系的有效方法
【发布时间】:2011-07-12 01:03:55
【问题描述】:

我在两个实体之间存在多对多关系 - 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);
            }
        }
    }

【问题讨论】:

    标签: entity-framework entity-framework-4.1


    【解决方案1】:

    你可以这样做,但你需要一个上下文:

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

    编辑:

    如果您使用带有代理 POCO 的 ObjectContext API 而不是 DbContext API,则前一个示例将不起作用。你可以试试这个:

    context.ContextOptions.LazyLoadingEnabled = false;
    bool exists = ((EntityCollection<Media>)mediaCollection.Media).CreateSourceQuery()
                                                                  .Any(x => x.Id == mediaId);
    context.ContextOptions.LazyLoadingEnabled = true;
    

    【讨论】:

    • 感谢您的帮助。我可以访问上下文,但此上下文是使用 T4 模板自动生成的(右键单击 > 添加 > 新项目 > ADO.NET POCO 实体生成器)。它似乎没有提供您在此处提到的方法,即 Entry()。
    • 那么,如果您不使用 EF 4.1,为什么还要用它来标记您的问题呢?
    • 嗯...我正在从我的数据库中生成我的 EF4.1 数据模型,然后使用内置的 VS POCO T4 实体模板来创建我的上下文和实体类。我尝试了您在编辑中的建议,但出现以下异常:Unable to cast object of type 'TheHoges.Domain.FixupCollection`1[TheHoges.Domain.Medium]' to type 'System.Data.Objects.DataClasses.EntityCollection`1[TheHoges.Domain.Medium]'.
    • 刚刚编辑了我的问题以使其更清晰并显示 T4 模板生成的相关代码。
    【解决方案2】:

    所以看起来内置的 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);
    

    支持 Rowan 在Using CreateSourceQuery in CTP4 Code First 上的回答。

    【讨论】:

      【解决方案3】:

      试试,

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

      CreateSourceQuery 将为关联创建 IQueryable。

      【讨论】:

      • 感谢您的帮助。但我的上下文是使用 T4 模板自动生成的(右键单击 > 添加 > 新项目 > ADO.NET POCO 实体生成器)。我的对象似乎没有 CreateSourceQuery() 方法。
      猜你喜欢
      • 1970-01-01
      • 2013-01-08
      • 2014-05-28
      • 1970-01-01
      • 1970-01-01
      • 2021-08-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多