为什么需要在客户端项目中引用EntityFramework.dll以使DbContext IDisposable?

时间:2016-06-30 18:37:05

标签: c# .net entity-framework

创建一个具有Entity Framework模型和对象上下文的类库。然后向解决方案添加新的控制台应用程序。从控制台应用程序中,引用具有您的模型的项目。

现在在控制台应用中输入:

static void Main(string[] args)
{
    using (var context = new ExperimentalDbContext())
    {

    }
    Console.ReadKey();
}

构建时,您会收到报告错误:

  

类型' System.Data.Entity.DbContext'在一个程序集中定义   没有引用。您必须添加对程序集的引用   EntityFramework ... yada yada yada ......

现在,在过去的几年里,我已经多次这样做了,但每当我收到这个错误时,我再一次无助地在互联网上搜索我当时忘记的解决方案。

要解决此问题,您需要在 ConsoleClient 项目中安装 EntityFramework NuGet包。

所以,我的问题不在于修复是什么,而是为什么?因为它没有任何意义!

为了完整起见,我使用的是实体框架的v6.1.3。但是,多年来我在早期版本中也多次看到过这个错误。

更新

只有在您使用using代码块时才会出现此问题,该代码块旨在Dispose上调用IDisposable

要测试该假设,请使用以下代码创建一个控制台应用程序,该应用程序在同一解决方案中引用ClassLibrary1,该解决方案在同一解决方案中引用ClassLibrary2:

using ClassLibrary1;
using System;

namespace TestHypothesis1
{
    class Program
    {
        // Testing the hypothesis presented in this answer: http://stackoverflow.com/a/38130945/303685
        // This seems to be the behavior with just (or may be even others I haven't tested for)
        // IDisposable.
        // anotherFoo instance is created just fine, but the moment I uncomment
        // the using statement code, it shrieks.
        static void Main(string[] args)
        {
            //using (var foo = new Foo())
            //{
            //    foo.Gar = "Gar";

            //    Console.WriteLine(foo.Gar);
            //}

            var anotherFoo = new Foo() { Gar = "Another gar" };
            Console.WriteLine(anotherFoo.Gar);

            Console.ReadKey();
        }
    }
}


using ClassLibrary2;
using System;

namespace ClassLibrary1
{
    public class Foo: Bar, IDisposable
    {
        public string Gar { get; set; }

        public void Dispose()
        {
            throw new NotImplementedException();
        }
    }
}


namespace ClassLibrary2
{
    public class Bar
    {
        public string Name { get; set; }
    }
}

您将观察到编译器仅针对第一个Foo的实例化而不是第二个实例的实例化而抱怨缺少引用。

奇怪的是,在第一个EntityFramework示例中,如果您从控制台应用程序中删除了对EntityFramework.dll的引用并将Main中的代码更改为此,它仍然会抱怨缺少引用。

static void Main(string[] args)
{
    var context = new ExperimentalDbContext();
    Console.ReadKey();
    context.Dispose();
}

此外,如果您对上面代码段的最后一行context.Dispose()发表评论,那么即使代码抛出InvalidOperationException,代码仍能正常运行,但我推测,该代码应该到期在迭代器完成MoveNext调用之前处理上下文的竞争条件。

static void Main(string[] args)
{
    var context = new ExperimentalDbContext();
    Console.ReadKey();
    // context.Dispose();
}

因此,新的附加问题现在变为:

实现using语句的方式是什么让编译器在链接引用中停止了?

最初的问题仍然存在。

又一次更新

现在看来问题可能会进一步归结为对IDisposable.Dispose方法的调用,因此问题不在于using语句的实现。 using声明似乎是一种无辜的保证Dispose将被调用,而不是其他任何内容。

因此,在上面的Foo示例中,如果在最后插入对anotherFoo.Dispose的调用,编译器会再次开始抱怨。像这样:

using ClassLibrary1;
using System;

namespace TestHypothesis1
{
    class Program
    {
        // Testing the hypothesis presented in this answer: http://stackoverflow.com/a/38130945/303685
        // This seems to be the behavior with just (or may be even others I haven't tested for)
        // IDisposable.
        // anotherFoo instance is created just fine, but the moment I uncomment
        // the using statement code, it shrieks.

        // Final update:
        // The trigger for the error seems to be the call to the Dispose method and not
        // particularly the implementation of the using statement, which apparently, simply
        // ensures that Dispose is called, as is also well-known and documented.
        static void Main(string[] args)
        {
            //using (var foo = new Foo())
            //{
            //    foo.Gar = "Gar";

            //    Console.WriteLine(foo.Gar);
            //}

            var anotherFoo = new Foo() { Gar = "Another gar" };
            Console.WriteLine(anotherFoo.Gar);

            anotherFoo.Dispose();

            Console.ReadKey();
        }
    }
}

因此,最后一个问题是:

为什么调用Dispose会阻止编译器链接汇编引用?

我想我们现在正在某个地方。

2 个答案:

答案 0 :(得分:1)

原始答案

我不认为它特定于DbContext,但或多或​​少是因为类库中引用的依赖DLL不会转移到控制台应用程序。因此,在构建时,编译器只知道控制台应用程序中的引用,而不知道EntityFramework的链接引用。它抱怨的唯一原因是因为编译器使用using语句运行检查以确保该类是IDisposable,并且它唯一可以知道的是它是否解析了内部的引用EntityFramework库。

更新

原来我仍然认为这是对的。如果在您的示例中,您忘记了IDisposable并且只是尝试在控制台应用程序中的Name类上使用属性Bar,那么您会发现它得到一个它不知道的异常属性,因为它是一个未引用的汇编。

未引用的程序集错误示例:

(inside Main)
Console.WriteLine(anotherFoo.Name);

为了它的价值,您实际上可以引用具有嵌套引用的库,并且永远不会在应用程序中包含这些嵌套引用,只要调用代码永远不会实际到达引用或需要嵌套库的代码路径。这可能是容易出错的错误来源,尤其是对于部署/发布方案。想象一下,您的发布不包含应用程序所需的所有库,但只需要很少调用需要深层嵌套库的代码路径。然后有一天,你接到一个电话,说“应用程序坏了!”一个人立即倾向于说“但没有改变!我们自上次以来没有部署过!”这是在测试,QA,部署后等方面获得良好代码覆盖率的重要原因之一。

答案 1 :(得分:1)

不是专门致电Dispose(),也不是与EF有关。它只是编译器对任何引用程序集的工作方式:如果要与当前项目不直接引用的程序集中定义的对象(读取:使用属性或方法)进行交互,你不能:你必须直接添加一个引用到该程序集。

此处发生的事情是,您致Dispose()的电话是从DbContext调用该方法,因为您尚未在第一级程序集中实现该方法。正如@poke在某些情况下指出 你可以通过在你的类上实现一个调用另一个程序集的方法来轻松解决这个问题:只要你不是这样的话就没关系试图直接访问它。这可能适用于某些情况下的人,但它不适用于EF上下文的特定情况。

问题是您的上下文将具有DbSet<your entity type>类型的属性,它本身是对System.Data.EntitySystem.Data命名空间的引用。在弄乱的时候,我发现这可以防止与“第一级”的任何互动。 object:它允许你新建一个就好了,但是第二个你尝试访问任何属性或方法时会得到相同的编译器错误。我假设这是由于编译器优化了代码,因此创建但从未使用过的对象实际上从未创建过。

只是为了清理你在其中一个更新中说的话:

  

使用声明似乎是一种无辜的保证,Dispose将被调用,而不是其他任何东西。

这是完全正确的。您可以在the spec的8.13中找到确切的详细信息,但一般用例是它扩展为

{
    ResourceType resource = expression;
    try {
        statement;
    }
    finally {
        ((IDisposable)resource).Dispose();
    }
}

基本上只是将语句包装在try...finally中,并将资源作用于新块。那里没有魔力。如上所述,您可以通过在第一级&#39;上实施IDisposable来解决此问题。上课并实施Dispose来呼叫第二级&#39;部件。在某些情况下,这可能会让你足够远。

以下是一些先前问题的例子,以更一般的方式询问:

  1. Why must I chain reference assemblies?
  2. Why do I (sometimes) have to reference assemblies referenced by the assembly I reference?
  3. Why does the compiler when using an overload in another assembly sometimes require you to also reference a subassembly?
  4. 有关显示此行为很常见的快速示例,请点击little repo on GitHub或查看以下代码:

    // our first, base class. put this in it's own project.
    namespace SecondLevel
    {
        public class SecondLevel
        {
            public void DoSomething()
            {
    
            }
        }
    }
    
    
    // our second class that references the base class. Add this to it's own project as well. 
    namespace FirstLevel
    {
        public class FirstLevel
        {
            public SecondLevel.SecondLevel reference;
    
            public FirstLevel()
            {
                reference = new SecondLevel.SecondLevel();
            }
    
            public void DoSomethingWithReferencedClass()
            {
                reference.DoSomething();
            }
        }
    }
    
    
    // our "Client" console app that does nothing, but indicates the compiler errors.
    // also in it's own project 
    // Reference the project that "FirstLevel" is in, 
    // but not the one that "Second Level" is in. 
    namespace ConsoleTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                var primary = new FirstLevel.FirstLevel();
    
                primary.reference.DoSomething(); // will cause compiler error telling you to reference "Second Level"
    
                primary.DoSomethingWithReferencedClass(); // no compiler error: does the same thing the right way.
            }
        }
    }
    

    请注意,primary.DoSomethingWithReferenceClass();将调用相同的方法,但没有错误,因为它不会直接尝试访问第二级类。

    关于为什么的问题,您必须向微软团队询问。解决方案是接受您必须添加引用,或者编写代码,以便客户端只知道引用程序集中的单个级别。