7

我有一个简单的类,旨在成为一个简单的 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 吗?

4

7 回答 7

8

您的 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);
    }
}

这对你有用吗?

于 2010-01-12T16:23:49.123 回答
7

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

于 2009-12-28T13:54:05.043 回答
2

另一种选择是使用从要检索的对象继承的代理(遵循一些对象关系映射器,如 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消费者不需要知道他们正在处理代理,也不需要与存储库交互(当然,除了任何类进行初始调用)。

于 2010-01-12T16:42:26.437 回答
2

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

于 2009-12-28T13:38:33.857 回答
1

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

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

于 2010-01-12T17:19:16.983 回答
1

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

因此,您可以在实体类中简单地编写如下内容:

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 实体并在那里实现存储库交互。

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

于 2010-01-09T01:58:22.830 回答
0

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

于 2010-01-08T23:21:18.943 回答