8

Make a class library that has your Entity Framework model and object context. Then add a new console application to the solution. From the console app, reference the project that has your model.

Now type this in the console app:

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

    }
    Console.ReadKey();
}

When you build, you'll get an error that reports:

The type 'System.Data.Entity.DbContext' is defined in an assembly that is not referenced. You must add a reference to assembly EntityFramework...yada yada yada...

Now, I have done this many times over the last few years, but every time I get this error, I once again helplessly search the Internet for the solution that I have forgotten by then.

The fix for this requires you to install the EntityFramework NuGet package in the ConsoleClient project.

So, my question is not about what the fix is, but rather why? Because it doesn't make any sense!

Just for the sake of completeness, I am using v6.1.3 of Entity Framework. But I have seen this error a number of times over the years with earlier versions as well.

Update

It appears that the problem is only when you use the using code block that is meant to call Dispose on IDisposables.

To test the hypothesis, create a console application, which references ClassLibrary1 in the same solution, which references ClassLibrary2 in the same solution, with the following code:

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; }
    }
}

And you will observe that the compiler complains about the missing reference only for the instantiation of the first Foo and not for the second instance.

Strangely though, in the first EntityFramework example, if you removed the reference to EntityFramework.dll from the console application and changed the code in the Main to this, it still complains about the missing reference.

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

Additionally, if you comment out the call to context.Dispose(), the last line of the code snippet above, the code still works fine even though it throws an InvalidOperationException but that, I surmise, is due to a race condition of the context being disposed before its iterator completes its MoveNext call.

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

So, the new additional question now becomes:

What is it about the way the using statement is implemented that makes the compiler stop in its tracks in linking references?

The original question also remains.

Yet another update

It now appears that the problem may be zeroed down further to the call to the IDisposable.Dispose method and the problem is therefore not with the implementation of the using statement. The using statement just seems to be an innocent assurance that Dispose will be called and nothing else.

Therefore, in the above Foo example, if you insert a call to anotherFoo.Dispose at the end, the compiler starts to complain again. Like so:

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();
        }
    }
}

So, the final question, then, in summary is this:

Why is it that the call to Dispose stops the compiler from being able to link assembly references?

I think we are now getting somewhere.

Water Cooler v2
  • 32,724
  • 54
  • 166
  • 336
  • 1
    How have you used your Context without requiring EntityFramework referenced within the project? – krillgar Jun 30 '16 at 18:39
  • @krillgar The *model* project should require it but not the *client*. It just doesn't make sense for the client to want a reference. I am asking why that is. – Water Cooler v2 Jun 30 '16 at 18:41

2 Answers2

2

Original Answer

I don't think it is specific to DbContext, but more or less it is because the dependent DLL's that are referenced in your class library are not carried over to the console application. Thus when you build, the compiler knows only about the references in the console application, and not the chained reference to EntityFramework. The only reason it is complaining is because the compiler runs a check with the using statement to ensure that the class is of IDisposable, and the only way it can know that is if it resolves the reference inside the EntityFramework library.

Update

Turns out I still think this is right. If, in your example, you forget IDisposable and just simply try to use the property Name on the Bar class in your console application, you will find that you get an exception that it does not know about that property, since it is in an unreferenced assembly.

Unreferenced assembly error example:

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

For what its worth, you can actually reference libraries that have nested references and never include those nested references in your application, so long as the calling code never actually reaches a code path that references or requires the nested libraries. This can be an easy source of bugs, especially for deployment/publish scenarios. Imagine a scenario where your publish does not include all the libraries needed for your app, but the code path that requires a deeply nested library only gets called rarely. Then one day, you get a phone call saying "The app broke!" and one immediately tends to say "But nothing changed! We haven't deployed since last time!" This is one of the highlight reasons for having good code coverage during testing, QA, post-deployment, etc.

ryancdotnet
  • 2,015
  • 16
  • 33
  • Damn! That doesn't *seem* right. Or may be I have been awake too long once again. It looks like I have some very basic stuff wrong in my head. Writing some test code to challenge this and validate my present belief, which is, that a reference shouldn't be required and should easily be inferred along the *chain*. – Water Cooler v2 Jun 30 '16 at 18:47
  • Actually, I am seeing some strange behavior for the first time with a little sample I wrote that kind of challenges this very basic thing I know. It works for most cases but doesn't for some. I'll post a .NET fiddle with my test code. – Water Cooler v2 Jun 30 '16 at 18:53
  • It appears that the problem is only when you use the IDisposable interface that it doesn't infer the correct references. See this example code. I wrote a console project, which referenced a ClassLibrary1 project which referenced ClassLibrary2 project. In the .NET fiddle, of course, I couldn't add projects but if you add them and run the code, you will observe this behavior I am asking about. https://dotnetfiddle.net/GcR1ea – Water Cooler v2 Jun 30 '16 at 18:58
  • Yes, I have updated the question. It seems to be the way the C# compiler implements the `using` statement. Now if @EricLippert or someone from within the C# team can tell us, that'll solve the mystery. :-) – Water Cooler v2 Jun 30 '16 at 19:15
  • I do think the compiler will always look for the configuration of the project you are running your code in. This does also mean that it will check your app.config (or web.config) of your project in order to create the context based on your connection string, and fail because there is no Entity Framework object defined. I believe it's not the Dispose() statement causing this Error, but the type definition itself since DbContext derives from IDisposable. – DevilSuichiro Jul 01 '16 at 07:07
1

It's not a call to Dispose() specifically, or anything to do with EF. It's just the way the compiler works for any referenced assembly: if you want to interact with objects (read: use properties or methods) that are defined in an assembly that's not directly referenced by the current project, you can't: you have to add a reference directly to that assembly.

What's happening here is your call to Dispose() is calling the method from DbContext because you haven't implemented it in your first-level assembly. As @poke pointed out in some cases you can easily get around this by implementing a method on your class that calls into the other assembly: that's fine as long as you aren't trying to access it directly. This may work for people in some scenarios but it doesn't work for the specific case here of the EF context.

The problem is your context will have properties of type DbSet<your entity type> which itself is a reference to the System.Data.Entity namespace in System.Data. While messing around I found that this prevents any interaction with the 'first level' object: it allows you to new one up just fine, but the second you try to access any property or method you will get the same compiler error. I assume this is due to the compiler optimizing the code so that an object that's created but never used never actually gets created.


Just to clear up something you said in one of your updates:

The using statement just seems to be an innocent assurance that Dispose will be called and nothing else.

This is exactly correct. You can find the exact details in 8.13 of the spec but the general use case is that it expands to

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

Basically just wrapping your statement in a try...finally with the resource scoped to a new block. There's no magic there. As mentioned you can work around this by implementing IDisposable on your 'first level' class and implementing Dispose there to call the 'second level' assembly. This might get you far enough in some cases.


Here are some examples of previous questions asking about this in a more general way:

  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?

For a quick example showing that this behavior is common, here's a little repo on GitHub or see the code below:

// 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.
        }
    }
}

Note that primary.DoSomethingWithReferenceClass(); will call the same method, but without an error, because it doesn't directly try to access the second-level class.

As to your question of why this is, you'd have to ask the Microsoft team. The resolution is to either accept that you have to add the reference, or to write your code so that the client only knows about a single level down into the referenced assemblies.

Community
  • 1
  • 1
DrewJordan
  • 5,266
  • 1
  • 25
  • 39
  • 1
    In OP’s situation, would implementing `IDisposable` again for the intermediary class (`ExperimentalDbContext`) solve this issue? Since `ExperimentalDbContext` would then implement `IDisposable` directly, the compiler wouldn’t need to go up to see the dispose implementation. – poke Jul 05 '16 at 18:22
  • @poke That's a great idea, I'm not sure why it didn't occur to me. Yes, that would work fine, you could just wrap the actual `DbContext.Dispose()` in `ExperimentalDbContext.Dispose()` and then you're only referencing the first reference, which would work fine. I'll add that to my answer if you don't mind. – DrewJordan Jul 05 '16 at 18:31
  • Sure, go ahead! And thanks for your answer, that definitely cleared up the situation for me – poke Jul 05 '16 at 18:38
  • 1
    @poke ugh, it's not that easy - seee my edit. Wrapping the call works fine in theory, but because the context class references `System.Data.Entity` for the `DbSet`s too you still get the error. It could be a good solution to be aware of if you come across this problem, but it's not going to allow you to work with EF without adding the reference. – DrewJordan Jul 05 '16 at 19:23