【问题标题】:Lazy Loading of Collection - how to get the items?收藏的延迟加载 - 如何获取物品?
【发布时间】:2009-12-28 13:37:17
【问题描述】:

我有一个简单的类,旨在成为一个简单的 POCO——它只保存数据。除了一个例外:它包含一组笔记。我想延迟加载这个集合,这样我就不必在不需要它们的页面上获取注释。存根是这样的:

public class MyDTOClass 
{
    private ICollection<Note> _notes = null;

    public ICollection<Note> Notes
    {
        get
        {
            if(_notes == null)
            {
                // Get an INoteRepository and initialize the collection
            }
            return _notes;
        }
    }
}

现在,我想知道如何从这里开始。这是一个 ASP.net MVC 应用程序,我使用依赖注入将 IRepositories 注入需要它们的类中,例如我的控制器。但是由于这里的这个类应该是一个非常简单的 DTO,我不愿意将 INoteRepository 注入其中,也是因为调用者不应该担心或关心这是延迟加载的事实。

所以我正在考虑在我的模型中创建另一个包含 INoteRepository 的类。

public class MyDataAccessClass
{
    private INoteRepository _noteRepo;

    // Inject is part of Ninject and makes sure I pass the correct
    // INoteRepository automatically
    [Inject]
    public MyDataAccessClass(INoteRepository noteRepository)
    {
        _noteRepo = noteRepository;
    }

    public IEnumerable<Note> GetNotes(int projectId)
    {
        return _noteRepo.GetNotes(projectId);
    }
}

这当然可行,但我想知道这是否是正确的架构?我将简单的 DTOClass 耦合到另一个数据访问类,也可能耦合到我的 DI 机制(因为我需要在 Notes 的 getter 中创建数据访问类的实例)。

你会做不同的事情吗?有没有更好的方法来做到这一点,同时记住我已经在使用 Ninject?

我猜这不再是 POCO 或 DTO,因为它现在包含逻辑,但没关系。我希望它像 POCO 一样出现给外部调用者,所以我喜欢在这个类或其他类上有一个属性“Notes”而不是像“GetNotesForProject”这样的方法。

我当前的解决方案真的很难看,因为我需要从我的 MvcApplication 获取 Ninject Kernel 并使用它来启动 ProjectDataProvider 类,该类在其构造函数中采用 INoteRepository,以避免将 INoteRepository 放在我的“DTO”中的某个位置"-类:

public ICollection<Note> Notes
{
    get
    {
        if(_notes == null)
        {
            var app = HttpContext.Current.ApplicationInstance as MvcApplication;
            if (app == null)
             throw new InvalidOperationException("Application couldn't be found");
            var pdp = app.Kernel.Get<ProjectDataProvider>();
            _notes = new List<Note>(pdp.GetNotes(Id));
        }
        return _notes;
    }
}

编辑:打开了一个赏金。让我们忽略“POCO”和“DTO”的术语,我会相应地重构。所以这是关于:在这种情况下延迟加载代码应该如何看待,我可以/应该避免将 INoteRepository 传递到 MyDTOClass 中吗?

【问题讨论】:

    标签: .net architecture ninject


    【解决方案1】:

    您的 DTO 不需要了解存储库本身。它所需要的只是一个可以为其提供注释值的委托。

    这样的事情怎么样:

    public class MyDTOClass
    {
        private ICollection<Note> _notes = null;
    
        public ICollection<Note> Notes
        {
            get
            {
                if (_notes == null)
                {
                    if (notesValueProvider == null)
                        throw new InvalidOperationException("ValueProvider for notes is invalid");
                    _notes = notesValueProvider();
                }
                return _notes;
            }
        }
    
        private Func<ICollection<Note>> notesValueProvider = null;
    
        public MyDTOClass(Func<ICollection<Note>> valueProvider)
        {
            notesValueProvider = valueProvider;
        }
    }
    

    因为根据定义,您的存储库应该为您提供 DTO 的实例,我们应该能够像这样传递值提供者委托:

    public class Repository
    {
        public MyDTOClass GetData()
        {
            MyDTOClass dto = new MyDTOClass(FetchNotes);
            return dto;
        }
    
        public ICollection<Note> FetchNotes()
        {
            return new List<Note>(200);
        }
    }
    

    这对你有用吗?

    【讨论】:

    • 这是 IMO 的最佳答案,它提供了解决方案,并且不会固有地使他的 DTO 类依赖于 IRepository,我很确定我已经看到这种模式以可重用的方式实现为 ILazy 或 AbstractLazy.
    • 是的。我认为这正是 .net 4 中的 Lazy 所做的。
    • 是的,这也是我会使用的方法。函数式编程 4tw。 =)
    • 这是一个很酷的方法,我会尝试一下。重点确实是我可以将它与 INoteRepository 完全解耦
    • 这里有几个非常好的选择,但我选择了这个,因为它在 my 应用程序中效果最好。
    【解决方案2】:

    如果您可以等待 .Net 4(即您还没有投入生产) Lazy(of T) 是 .Net 框架中的一个新的延迟加载功能。 http://msdn.microsoft.com/en-us/library/dd642331(VS.100).aspx

    【讨论】:

    • 太棒了!遗憾的是,我等不及这个项目了,但我一定会牢记这一点。
    【解决方案3】:

    当您尝试向 DTO 添加延迟加载逻辑时,您就违背了 DTO 的全部目的。我认为,您应该有两个单独的对象:一个带有 Notes,另一个 - 没有它们。

    【讨论】:

    • 可能,但我不确定是否要破坏它。您是否建议在没有注释的情况下使用 MyDTOClass,然后将其子类化为 MyDTOClassWithNotes :添加注释的 MyDTOClass(忽略错误的命名)?我仍然可以将 WithNotes 类传递给只需要 MyDTOClass 的函数。我只是不知道是否有什么反对它,当然除了增加一堂课。
    • 我同意安东的观点。您的 DTO 应该准确反映视图的需求。如果它需要注释,DTO 应该包含注释。如果视图需要延迟加载注释,则为视图提供不带注释的 DTO,并为视图(部分视图?)提供一种机制,以使用控制器为给定类执行其自己的注释的延迟加载旨在做到这一点。这使域类、DTO、控制器和视图保持纯粹和简单。
    【解决方案4】:

    另一种选择是使用从您要检索的对象继承的代理(遵循一些对象关系映射器,如 NHibernate)。

    这通过将数据访问代码与域模型分开来提供一定程度的持久性无知:

    public class MyLazyDTOClass: MyDTOClass {   
    
        // Injected into the constructor by the MyDtoClass repository
        private INoteRepository noteRepository;        
    
        public ICollection<Note> Notes {
            get {
                if(base.Notes == null) {
                    base.Notes = noteRepository.GetNotes(projectId);
                }
                return base.Notes;
            }
        }
    }
    

    MyDTOClassRepository 将基对象声明为其返回类型,但返回惰性对象:

    public MyDTOClassRepository {
        public MyDTOClass GetMyDTOClass(int id) {
            // ... Execute data access code to obtain state ...
            return new MyLazyDTOClass(this, state);
        }
    }
    

    MyDTOClass 消费者不需要知道他们正在处理代理,也不需要与存储库进行交互(当然,进行初始调用的任何类除外)。

    【讨论】:

    • 有趣的想法。通过将有关 INoteRepository 的知识提供给 MyDTOClassRepository(也许 Factory 是一个更好的术语)类,即使是最初的调用者也可能解耦。必须尝试一下。
    【解决方案5】:

    在绝望地寻找答案在星界漫游了数万年之后,我得出了最终的结论,是的,没有必要将存储库传递到您的实体实例中,因为实体类型的存储库应该始终是单身人士。

    所以你可以在你的实体类中简单地写如下:

    public class Monster
    {
        public ReadOnlyCollection<Victim> _victims;
        public ReadOnlyCollection<Victim> Victims
        {
            get
            {
                if (this._victims == null) // Note: Thread-Safety left out for brevity
                {
                    this._victims = VictimRepository.Instance.GetVictimsForMonster(this);
                }
    
                return this._victims;
            }
        }
    }
    

    这真的解决了我所有的头痛。

    存储库必须以始终知道如何处理数据的方式实施。

    请记住,例如,一种存储库实现会从数据库中获取数据,而另一种可能会从 Web 服务中获取数据。由于松耦合,存储库实现模块易于替换,任何数据通信甚至可以任意链接。

    如果你有一个场景,你会说“但存储库不能是单例的,因为我有一个复杂的场景,例如我访问多个数据源,它取决于实际的 Monster 实例从哪里获取Victims 来自",那么我说你必须创建一个存储库实现,它知道所有数据源并跟踪实体实例的来源和去向,等等......

    如果您觉得这种方法不够 POCO,那么您必须创建另一个松散耦合的业务逻辑层,它包装或派生自 POCO 实体并在那里实现存储库交互。

    我希望我能给一个适合你和任何人的方向的提示。我确实相信这是多层/层级开发的圣杯。欢迎进一步讨论。

    【讨论】:

    • 我在考虑类似的事情,问题是我不知道实现存储库的类的名称,因为它是通过依赖注入创建的。但我猜我需要一个静态工厂类...
    • 是的,肯定有更多的方法可以做到这一点。我以前也摆弄过 IMonsterRepository 和工厂之类的东西,但毕竟,如果它是单例的,我只需要一个用于存储库的抽象基类。静态实例属性将调用一个解析器,该解析器知道如何为存储库创建一个实例,然后将其缓存。
    【解决方案6】:

    如果您从存储库中延迟加载,则无法实现独立于存储库。您可以通过使用回调或代理或让 NHibernate 为您完成繁琐的工作来保持距离,但您的 DTO 必须访问存储库才能加载 Notes。

    您的主要目标似乎是“我希望它对外部调用者看起来像一个 POCO,所以我喜欢在这个或其他类上拥有一个属性“Notes”而不是像“GetNotesForProject”这样的方法。”你不能用 ninject 和构造函数注入来完成这个吗?设置 ninject 后,您可以调用 kernel.Get() 来获取一个不会公开对您的存储库的任何引用的新实例。

    【讨论】:

    • 是的,这就是我现在正在做的事情,但是正如您在上面看到的,没有真正方便的方法来访问位于 MvcApplication 中的内核。我已经打开了这个问题,看看最好的方法是什么:将内核移动到静态类中以使其全局可用,或者以某种方式将应用程序解耦到足以使我的“DTO”不需要内核或通过的存储库在。
    【解决方案7】:

    您可以让 Notes 属性将 INoteRepository 作为参数。这样调用代码就可以传入正确的 INoteRepository 实例。

    【讨论】:

    • 这将使它成为一种方法,我希望 MyDTOClass 的消费者不知道/关心这一点 - 它应该是透明的。否则我可能会把它完全移出课堂。
    • 我的错,我最近一直在使用 VB.NET,它允许将参数传递给属性,但忘记了 C# 不支持这一点。 :-(
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多