懒加载 - 最佳实践是什么?

14

我看过很多懒加载的例子 - 你的选择是什么?

假设有一个模型类:

public class Person
{
    private IList<Child> _children;
    public IList<Child> Children
    {
        get {
            if (_children == null)
                LoadChildren();
            return _children;
        }
    }
}

Person类不应该知道它的子类如何加载...或者说它应该吗?它肯定应该控制属性何时被填充,或者不填充?

你会有一个将Person和它的子集合耦合在一起的存储库吗?还是会使用不同的方法,比如使用lazyload类-即使这样,我也不想让懒加载类模糊我的模型架构。

如果首先请求Person,然后请求其Children(即在此实例中不进行懒加载),或以某种方式进行懒加载,您将如何处理性能?

所有这些归结为个人选择吗?


问题 - 这往往是以牺牲全局优化为代价的局部优化的典型例子。 - dkretz
7个回答

14
最好的懒加载是避免使用它;线程安全是您必须处理的一个问题。在我看来,生产系统中经常会出现8个CPU核心运行8次懒加载,而每个懒加载模式都在使用的情况,这个数字无法计算。至少在服务器启动时,所有服务器核心都有趋向于聚集到同一位置。
如果可以的话,让DI框架为您构建它。如果不能,则仍然更喜欢显式构造。所以所有种类的AOP魔法对我来说都不起作用,请在类外部进行显式构造。不要将其放在Person类内部,只需创建一个能以正确方式构建对象的服务即可。
引入“神奇”的层次结构似乎是个不错的想法,但我还没有遇到过没有未预见和问题的后果的实现。

感谢您的时间,您能详细说明如何使用 DI 框架和显式构造吗? - Tim Peel
此外,您会失去查询的原子性。而且往往最有效的整体策略是发出一个带有(外部?)连接的单个查询来获取所有子记录,并在一个事务中完成,而不是将其分散在多个数据库子命中中。 - dkretz
@Krosenvold - 不会有任何冒犯 ;) - 我也不喜欢那些“神奇”的层。在我看来,它们会让情况变得混乱。所以你的方法和@Toran的方法类似吗? - Tim Peel
4
@Le Dorfier - 同意性能方面的担忧。然而,一个对象可能有太多的子关系,不值得一次性加载所有内容,会出现一个“临界点”。在这种情况下,你会怎么做? - Tim Peel
2
我还想知道在“太多子关系”的情况下该怎么办。 - Jack Ukleja
显示剩余2条评论

4

1

我谈到了一种我用来实现延迟加载的解决方案这里


谢谢您的时间,但这不仅仅是另一个依赖关系吗?尽管如此,我对这种方法很感兴趣。 - Tim Peel

1
你可以使用虚拟代理模式,结合观察者模式。这样可以实现延迟加载,而不需要Person类显式地知道如何加载Children。

0

我认为这恰好是最适合由AOP(例如PostSharp)处理的问题类型。 将您的惰性加载作为一个方面,然后使用它来装饰您想要懒加载的任何属性。 免责声明:尚未尝试过; 只是认为它应该可以奏效。


0

我刚刚在这里问了一个相关的问题,但它更加重视不可变性和线程安全性。有很多好的答案和评论。你可能会发现它很有用。


0
这是一个使用代理模式实现延迟加载的示例。
Person类将与您的其他模型一起存在。Children被标记为虚拟,因此它可以在PersonProxy类中被覆盖。
public class Person {
    public int Id;
    public virtual IList<Child> Children { get; set; }
}

PersonRepository类将与其余存储库一起使用。 我在这个类中包括了获取子项的方法,但如果您愿意,也可以将其放在ChildRepository类中。

public class PersonRepository {
    public Person FindById(int id) {
        // Notice we are creating PersonProxy and not Person
        Person person = new PersonProxy();

        // Set person properties based on data from the database

        return person;
    }

    public IList<Child> GetChildrenForPerson(int personId) {
        // Return your list of children from the database
    }
}

PersonProxy类与您的存储库一起使用。它继承自Person并执行延迟加载。您还可以使用布尔值来检查是否已经加载,而不是检查Children == null。

public class PersonProxy : Person {
    private PersonRepository _personRepository = new PersonRepository();

    public override IList<Child> Children {
        get {
            if (base.Children == null)
                base.Children = _personRepository.GetChildrenForPerson(this.Id);

            return base.Children;
        }
        set { base.Children = value; }
    }
}

你可以这样使用它。
Person person = new PersonRepository().FindById(1);
Console.WriteLine(person.Children.Count);

当然,如果您不想直接调用PersonRepository,您可以让PersonProxy接受一个指向PersonRepository的接口,并通过服务访问它。

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