如何使用实体框架将实体加载到私有集合中

7
我有一个POCO领域模型,使用新的ObjectContext类将其与实体框架连接起来。
public class Product
    {
        private ICollection<Photo> _photos;

        public Product()
        {
            _photos = new Collection<Photo>();         
        }

        public int Id { get; set; }
        public string Name { get; set; }
        public virtual IEnumerable<Photo> Photos
        {
            get
            {
                return _photos;
            }
        }

        public void AddPhoto(Photo photo)
        {
            //Some biz logic
            //...
            _photos.Add(photo);
        }
    }

在上面的示例中,我将Photos集合类型设置为IEnumerable,因为这样会使其只读。唯一添加/删除照片的方法是通过公共方法。
问题在于,Entity Framework无法将Photo实体加载到IEnumerable集合中,因为它不是ICollection类型。
将类型更改为ICollection将允许调用者在集合本身上调用Add方法,这并不好。
我的选择是什么?
编辑:
我可以重构代码,使其不公开Photos的公共属性:
public class Product
    {
    public Product()
    {
        Photos = new Collection<Photo>();         
    }

    public int Id { get; set; }
    public string Name { get; set; }
    private Collection<Photo> Photos {get; set; }

    public IEnumerable<Photo> GetPhotos()
    {
        return Photos; 
    }

    public void AddPhoto(Photo photo)
    {
        //Some biz logic
        //...
        Photos.Add(photo);
    }

    }

使用GetPhotos()方法返回集合。这种方法的另一个问题是,我将失去更改跟踪功能,因为我无法将集合标记为虚拟 - 不可能将属性标记为私有虚拟。
在NHibernate中,我认为可以通过配置将代理类映射到私有集合。我希望这将成为EF4的一个特性。目前,我不喜欢无法对集合进行任何控制的情况!

我希望我能找到这个问题的答案! - Rushino
5个回答

5

做到这一点的方法是有一个受保护的虚拟属性,在您的模型中映射该属性并有一个公共属性返回一个IEnumerable。

public class Product
{
    public Product()
    {
        PhotoCollection = new Collcation<Photo>();
    }

    public int Id { get; set; }
    public string Name { get; set; }
    protected virtual ICollection<Photo> PhotoCollection {get; set; }

    public IEnumerable<Photo> Photos
    {
        get { return PhotoCollection ; } 
    }

    public void AddPhoto(Photo photo)
    {
        //Some biz logic
        //...
        PhotoCollection .Add(photo);
    }
}

1
这个问题在于:Photos 无法与 LINQ to Entities 兼容,这就需要使用 ToList() 强制转换,从而导致更多的数据库查询,例如 Product.Photos.ToList().Where(p=> .... - MrEdmundo

1
Anton,如果您能解释一下为什么不希望开发人员访问您的集合的Add方法,那将有助于我更好地理解您的问题。这是因为列表是严格只读的,还是因为您想在添加新实体时运行一些自定义业务逻辑?
无论如何...我假设您正在尝试做后者(即在修改集合时运行自定义业务逻辑)。我在我的一个项目中做了类似的解决方案,思路如下:
EF4中生成POCO的TT模板将所有集合创建为TrackableCollection列表。该类具有名为“CollectionChanged”的事件,您可以订阅并监听对集合的任何更改。
因此,您可以执行以下操作:
public class Product
{
    public Product()
    {
        Photos.CollectionChanged += ListCollectionChanged;
    }

    public int Id { get; set; }

    public string Name { get; set; }

    public TrackableCollection<Photo> Photos
    {
        get
        {
            // default code generated by EF4 TT
        }
        set
        {
            // default code generated by EF4 TT
        }
    }

    private void ListCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            // A new item has been added to collection
            case NotifyCollectionChangedAction.Add:
                {
                    T newItem = (T) e.NewItems[0];
                    // Run custom business logic
                }
                break;

            // An existing item has been removed
            case NotifyCollectionChangedAction.Remove:
                {
                    T oldItem = (T) e.OldItems[0];
                    // Run custom business logic
                }
                break;
        }
    }
}

上述解决方案的好处在于,您仍然可以以“EF”的方式使用产品实体...您团队中的任何开发人员都可以简单地访问实体目录的属性,而无需运行显式的硬类型函数。

0

虽然有点晚了,但这就是Observable对象的用途。让数据结构发挥其所长。如果您不想构建自己需要的集合并从属性中公开常规ICollection类型,则可以使用ObservableCollection作为字段类型。当集合中的相关实体通过CollectionChanged事件更改时,您可以在父实体中运行任何所需的逻辑。如果您需要有选择地启用或禁用修改,那么很容易扩展现有的集合类型或编写代理集合,以允许调用方法来切换集合的可变性(ISupportInitialize可以用于表示此功能)。


-1

(为我的简短初始帖子道歉-我当时是用手机回答的)

您可以通过对EF实体集合进行LINQ查询来构建您的集合。然而,您将结果集合作为内部数据成员保留在业务类中,并通过调用实体集上的AsEnumerable()返回的IEnumerable<Photo>作为公共照片的结果进行公开。

您也可以在内部缓存IEnumerable<Photos>,这样您就不必每次调用方请求集合时都调用AsEnumerable()。当然,这意味着如果用户需要通过您的公共方法更新集合,则可能必须刷新缓存的IEnumerable。如果调用者还缓存了对先前IEnumerable的指针,则可能会出现小问题。

或者,如果您的调用者始终使用完整的实体集合进行工作,则EntitySet类(您的所有EF集合都将继承)实现了IEnumerable<TEntity>,因此您可以直接将实体集返回给您的调用者。

请注意,如果您希望从 EF 实体集加载集合发生在业务类的范围之外,您可以在类上创建一个构造函数,该构造函数接受一个 ICollection。这样,一旦您创建了对象,集合就被封装在其中,并且仅作为 IEnumerable 公开。

我不明白在这种情况下调用AsEnumerable的作用是什么?你能再解释一下吗? - Anton P
我们的领域层与数据层是分离的。我正在使用存储库模式将 POCO 对象从数据层传递到我们的业务对象。你建议的完全可行,只是数据库逻辑已经渗入到了领域/业务模型中,有些人可能会说这不是纯粹的 POCO。 - Anton P
我明白了。在这种情况下,我会创建一个带有 ICollection 参数的显式构造函数的 POCO 对象(以您的示例中的照片为例)。这样,您的数据层将负责实例化 POCO 对象并从 EF 中加载数据,而您的 POCO 对象仍将将数据公开为 IEnumerable。业务对象的任何客户端都无法直接更新其集合。我会在我的答案中加入这个建议。 - Franci Penov
ReadOnlyCollection<T>怎么样? - Isaac Abraham
@Isaac Abraham - ReadOnlyCollection<T>IEnumerable<T>方法并没有区别 - 它仍然会防止EF通过Photos属性加载POCO对象,这是OP的问题。 - Franci Penov
显示剩余2条评论

-1

为什么不尝试以下方法,使用属性并离开?

private ICollection<Photo> photos{get; set;}
public IEnumerable<Photo> Photos
{
    get {return (IEnumberable<Photo>)photos;}
}

或者你可以使用装饰器模式将类封装成一个集合无法直接修改的类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接