如果访问的导航属性未加载(并且禁用了延迟加载),则将EF配置为throw

时间:2012-02-09 16:53:32

标签: entity-framework entity-framework-4 lazy-loading eager-loading

我们有一些应用程序正在使用启用了延迟加载的EF模型。当我关闭延迟加载(以避免隐式加载和我们的大多数N + 1选择)时,我更愿意访问应该已经加载的(或者在引用上手动加载())抛出异常而不是返回null(因为这个特定的异常比null ref更好更容易调试)。

我目前倾向于仅修改t4模板(因此,如果reference.IsLoaded == false,throw),但是想知道这是否已经解决了问题,无论是在框中还是通过其他项目。

对插件/扩展/等的任何引用的加分点,可以进行源分析并检测此类问题。 :)

3 个答案:

答案 0 :(得分:2)

我想做同样的事情(抛出延迟加载)出于几个与性能相关的原因 - 我想避免同步查询,因为它们会阻塞线程,在某些地方我想避免加载一个完整的实体而只是加载代码所需的属性。

禁用延迟加载并不是很好,因为某些实体的属性可以合法地为空,我不想混淆" null因为它是空的"使用" null因为我们决定不加载它"。

我还想在一些特定的代码路径中选择性地抛出延迟加载,我知道延迟加载是有问题的。

以下是我的解决方案。

在我的DbContext类中,添加以下属性:

$ ./bin/structstudents <dat/studentclass.txt

students:

 Smith, Eric                                                  Biology
 Williams, Wade                                               Biology
 Johnson, John                                                Math

在我的代码启动的某个地方,运行:

qsort

class AnimalContext : DbContext { public bool ThrowOnSyncQuery { get; set; } } 的代码如下:

// Optionally don't let EF execute sync queries
DbInterception.Add(new ThrowOnSyncQueryInterceptor());

然后在使用ThrowOnSyncQueryInterceptor

的代码中
public class ThrowOnSyncQueryInterceptor : IDbCommandInterceptor
{
    public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    {
        OptionallyThrowOnSyncQuery(interceptionContext);
    }

    public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    {
    }

    public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        OptionallyThrowOnSyncQuery(interceptionContext);
    }

    public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
    }

    public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
        OptionallyThrowOnSyncQuery(interceptionContext);
    }

    public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
    }

    private void OptionallyThrowOnSyncQuery<T>(DbCommandInterceptionContext<T> interceptionContext)
    {
        // Short-cut return on async queries.
        if (interceptionContext.IsAsync)
        {
            return;
        }

        // Throw if ThrowOnSyncQuery is enabled
        AnimalContext context = interceptionContext.DbContexts.OfType<AnimalContext>().SingleOrDefault();
        if (context != null && context.ThrowOnSyncQuery)
        {
            throw new InvalidOperationException("Sync query is disallowed in this context.");
        }
    }
}

答案 1 :(得分:0)

您不必修改T4。基于提到“T4”,我猜你正在使用EDMX。容器的属性窗口具有lazyloadingenabled属性。创建新模型时,它设置为true。您可以将其更改为false。 T4模板将看到并将代码添加到ctor中。

此外,如果您使用的是Microsoft的POCO模板,他们会将虚拟关键字添加到您的导航属性中。 Virtual + lazyloadingenabled是获得延迟加载的必要组合。如果删除虚拟关键字,那么即使启用了延迟加载,该属性也不会延迟加载。

HTH 朱莉

答案 2 :(得分:0)

项目https://github.com/jamesmanning/EntityFramework.LazyLoadLoggingInterceptor的创建者

jamesmanning通过读取堆栈跟踪来设法拦截延迟加载的调用。

因此,您可以创建DbCommandInterceptor,其功能类似于:

    public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        // unfortunately not a better way to detect whether the load is lazy or explicit via interceptor
        var stackFrames = new StackTrace(true).GetFrames();
        var stackMethods = stackFrames?.Select(x => x.GetMethod()).ToList();

        var dynamicProxyPropertyGetterMethod = stackMethods?
            .FirstOrDefault(x =>
                x.DeclaringType?.FullName.StartsWith("System.Data.Entity.DynamicProxies") == true &&
                x.Name.StartsWith("get_"));

        if (dynamicProxyPropertyGetterMethod != null)
        {
              throw new LazyLoadingDisallowedException();
        }

我知道读取堆栈跟踪帧可能会很昂贵,尽管我的猜测是在发生数据访问的正常情况下,与数据访问本身相比,成本可以忽略不计。但是,您将需要自己评估这种方法的性能。

(作为附带说明,您所追求的是NHibernate多年来拥有的许多不错的功能之一)。