关于C#:如何使用实体框架将实体加载到私有集合中

How to load entities into private collections using the entity framework

我有一个POCO域模型,它使用新的ObjectContext类连接到实体框架。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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);
        }
    }

在上面的示例中,我将照片集合类型设置为IEnumerable,因为这将使其成为只读的。添加/删除照片的唯一方法是通过公共方法。

问题在于,实体框架无法将照片实体加载到IEnumerable集合中,因为它不属于ICollection类型。

通过将类型更改为ICollection,调用方可以调用集合本身不好的add mentod。

我有什么选择?

编辑:

我可以重构代码,这样它就不会公开照片的公共属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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的一个特性。目前我不喜欢无法控制收藏!


这样做的方法是拥有一个在模型中映射的受保护虚拟属性和一个返回IEnumerable的公共属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
    }
}


安东,如果你能解释为什么你不希望开发人员访问你的集合的添加方法,这将帮助我更多地理解你的问题。这是因为列表是严格只读的,还是因为添加新实体时要运行一些自定义业务逻辑?

总之…我假设您正在尝试执行后者(即,在修改集合时运行自定义业务逻辑)。我在我的一个项目上做了类似的解决方案,想法如下:

在EF4中生成POCO的TT模板将所有集合创建为可跟踪集合列表。此类具有一个名为"CollectionChanged"的事件,您可以订阅并侦听对集合的任何更改。

所以你可以做如下的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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"的方式使用产品实体…如果团队中的任何开发人员都可以简单地访问实体目录的属性,并且需要运行一个显式的硬类型函数。


参加聚会有点晚,但这就是可观察物体的作用。允许数据结构尽其所能。如果不想构建自己的集合来满足需要并从属性中公开常规ICollection类型,请使用ObservableCollection作为字段类型。当集合中的相关实体通过CollectionChanged事件更改时,可以在父实体中运行所需的任何逻辑。如果需要有选择地启用或禁用修改,则很容易扩展现有的集合类型或编写代理集合,从而允许调用方法来切换集合的可变性(isupportInitialize可用于表示此功能btw的良好效果)。


为什么不尝试以下操作并保留使用属性?

1
2
3
4
5
private ICollection<Photo> photos{get; set;}
public IEnumerable<Photo> Photos
{
    get {return (IEnumberable<Photo>)photos;}
}

或者,您可以使用decorator模式将类封装到不能直接修改集合的类中。


(为我最初的简短帖子道歉-我是从电话里接的)

可以通过对EF实体集的LINQ查询来构造集合。但是,您将生成的集合作为内部数据成员保留在您的业务类中,并公开由于公共照片而对实体集调用AsEnumerable()返回的IEnumerable

您也可以在内部缓存IEnumerable,这样您就不会在每次您的呼叫者要求收集时都给AsEnumerable()打电话。当然,这意味着如果用户需要通过公共方法更新集合,那么您可能需要刷新缓存的IEnumerable。如果调用者还缓存了指向前一个IEnumerable的指针,这可能会造成小问题。

或者,如果调用者总是使用完整的实体集,那么EntitySet类(其中所有ef集都将继承)实现IEnumerable,因此您可以直接将实体集返回调用者。

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