Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested DbContext due to method calls - Entity Framework

In the following case where two DbContexts are nested due to method calls:

public void Method_A() {
    using (var db = new SomeDbContext()) {
        //...do some work here
        Method_B();
        //...do some more work here
    }
}

public void Method_B() {
    using (var db = new SomeDbContext()) {
        //...do some work
    }
}

Question:

  1. Will this nesting cause any issues? (and will the correct DbContext be disposed at the correct time?)

  2. Is this nesting considered bad practice, should Method_A be refactored into:

    public void Method_A() {
        using (var db = new SomeDbContext()) {
            //...do some work here
        }
    
        Method_B();
    
        using (var db = new SomeDbContext()) {
            //...do some more work here
        }
    }
    

Thanks.

like image 509
Tech Fun Avatar asked Oct 16 '12 10:10

Tech Fun


3 Answers

Your DbContext derived class is actually managing at least three things for you here:

  • the metadata that describes your database and your entity model,
  • the underlying database connection, and
  • a client side "cache" of entities loaded using the context, for change tracking, relationship fixup, etc. (Note that although I term this a "cache" for want of a better word, this is generally short lived and is just to support EFs functionality. It's not a substitute for proper caching in your application if applicable.)

Entity Framework generally caches the metadata (item 1) so that it is shared by all context instances (or, at least, all instances that use the same connection string). So here that gives you no cause for concern.

As mentioned in other comments, your code results in using two database connections. This may or may not be a problem for you.

You also end up with two client caches (item 3). If you happen to load an entity from the outer context, then again from the inner context, you will have two copies of it in memory. This would definitely be confusing, and could lead to subtle bugs. This means that, if you don't want to use shared context objects, then your option 2 would probably be better than option 1.

If you are using transactions, there are further considerations. Having multiple database connections is likely to result in transactions being promoted to distributed transactions, which is probably not what you want. Since you didn't make any mention of db transactions, I won't go into this further here.

So, where does this leave you?

If you are using this pattern simply to avoid passing DbContext objects around in your code, then you would probably be better off refactoring MethodB to receive the context as a parameter. The question of how long-lived context objects should be comes up repeatedly. As a rule of thumb, create a new context for a single database operation or for a series of related database operations. (See, for example this blog post and this question.)

(As an alternative, you could add a constructor to your DbContext derived class that receives an existing connection. Then you could share the same connection between multiple contexts.)

One useful pattern is to write your own class that creates a context object and stores it as a private field or property. Then you make your class implement IDisposable and its Dispose() method disposes the context object. Your calling code news up an instance of your class, and doesn't have to worry about contexts or connections at all.

When might you need to have multiple contexts active at the same time?

This can be useful when you need to write code that is multi-threaded. A database connection is not thread-safe, so you must only ever access a connection (and therefore an EF context) from one thread at a time. If that is too restrictive, you need multiple connections (and contexts), one per thread. You might find this interesting.

like image 90
Olly Avatar answered Nov 18 '22 02:11

Olly


You can alter your code by passing to Method_B the context. If you do so, the creation of the second db call SomeDbContext will not be necessary.

there a question an answer in stackoverflow in this link Proper use of "Using" statement for datacontext

like image 23
DadViegas Avatar answered Nov 18 '22 03:11

DadViegas


It is a bit late answer, but still people may be looking so here is another way.

Create class, that cares about disposing for you. In some scenarios, there would be a function usable from different places in solution. This way you avoid creating multiple instances of DbContext and you can use nested calls as many as you like.

Pasting simple example.

public class SomeContext : SomeDbContext
{
    protected int UsingCount = 0;
    public static SomeContext GetContext(SomeContext context)
    {
        if (context != null)
        {
            context.UsingCount++;
        }
        else
        {
            context = new SomeContext();
        }
        return context;
    }

    private SomeContext()
    {
    }

    protected bool MyDisposing = true;
    protected override void Dispose(bool disposing)
    {
        if (UsingCount == 0)
        {
            base.Dispose(MyDisposing);
            MyDisposing = false;
        }
        else
        {
            UsingCount--;
        }
    }

    public override int SaveChanges()
    {
        if (UsingCount == 0)
        {
            return base.SaveChanges();
        }
        else
        {
            return 0;
        }
    }
}

Example of usage

public class ExmapleNesting
{
    public void MethodA()
    {
        using (var context = SomeContext.GetContext(null))
        {
            // manipulate, save it, just do not call Dispose on context in using
            MethodB(context);
        }

        MethodB();
    }

    public void MethodB(SomeContext someContext = null)
    {
        using (var context = SomeContext.GetContext(someContext))
        {
            // manipulate, save it, just do not call Dispose on context in using
            // Even more nested functions if you'd like
        }
    }
}

Simple and easy to use.

like image 1
FossilMFC Avatar answered Nov 18 '22 02:11

FossilMFC