0

I have a web application where I have just began to use Entity Framework. I read the beginners tutorials, and topics about benefits of object context per request for web apps. However, I am not sure my context is at the right place...

I found this very useful post (Entity Framework Object Context per request in ASP.NET?) and used the suggested code :

public static class DbContextManager
{
    public static MyEntities Current
    {
        get
        {
            var key = "MyDb_" + HttpContext.Current.GetHashCode().ToString("x")
                      + Thread.CurrentContext.ContextID.ToString();
            var context = HttpContext.Current.Items[key] as MyEntities;

            if (context == null)
            {
                context = new MyEntities();
                HttpContext.Current.Items[key] = context;
            }
            return context;
        }
    }
}

And in Global.asax :

protected virtual void Application_EndRequest()
{
    var key = "MyDb_" + HttpContext.Current.GetHashCode().ToString("x")
                      + Thread.CurrentContext.ContextID.ToString();
    var context = HttpContext.Current.Items[key] as MyEntities;

    if (context != null)
    {
        context.Dispose();
    }
}

Then, I am using it in my pages :

public partial class Login : System.Web.UI.Page
{
    private MyEntities context;
    private User user;

    protected void Page_Load(object sender, EventArgs e)
    {
        context = DbContextManager.Current;

        if (Membership.GetUser() != null)
        {
            Guid guid = (Guid)Membership.GetUser().ProviderUserKey;
            user = context.Users.Single(u => (u.Id == guid));
        }
    }

    protected void _Button_Click(object sender, EventArgs e)
    {
        Item item = context.Items.Single(i => i.UserId == user.Id);
        item.SomeFunctionThatUpdatesProperties();
        context.SaveChanges();
    }
}

I did read a lot but this is still a little bit confused for me. Is the context getter okay in Page_Load ? Do I still need to use "using" or will disposal be okay with the Global.asax method ?

If I am confusing something I am sorry and I would be really, really grateful if someone could help me understand where it should be.

Thanks a lot !

Edits following nativehr answer and comments :

Here is the DbContextManager:

public static class DbContextManager
{
    public static MyEntities Current
    {
        get
        {
            var key = "MyDb_" + typeof(MyEntities).ToString();
            var context = HttpContext.Current.Items[key] as MyEntities;

            if (context == null)
            {
                context = new MyEntities();
                HttpContext.Current.Items[key] = context;
            }
            return context;
        }
    }
}

The page :

public partial class Login : System.Web.UI.Page
{
    private User user;

    protected void Page_Load(object sender, EventArgs e)
    {
        if (Membership.GetUser() != null)
        {
            Guid guid = (Guid)Membership.GetUser().ProviderUserKey;
            user = UserService.Get(guid);
        }
    }

    protected void _Button_Click(object sender, EventArgs e)
    {
        if (user != null)
        {
            Item item = ItemService.GetByUser(user.Id)
            item.SomeFunctionThatUpdatesProperties();
            ItemService.Save(item);
        }
    }
}

And the ItemService class :

public static class ItemService
{
    public static Item GetByUser(Guid userId)
    {
        using (MyEntities context = DbContextManager.Current)
        {
            return context.Items.Single(i => (i.UserId == userId));
        }
    }

    public static void Save(Item item)
    {
        using (MyEntities context = DbContextManager.Current)
        {
            context.SaveChanges();
        }
    }
}
Community
  • 1
  • 1
Flash_Back
  • 565
  • 3
  • 8
  • 31
  • 1
    I don't like very much to keep context for all request (especially if your web site has very high traffic) but yes, it works and you do NOT need to use using and/or to dispose context in any other place than Application_EndRequest (as you're doing) then your code is perfectly fine. – Adriano Repetti Mar 11 '15 at 11:06
  • Consider that global context instances are suitable if the data exposed through the context is used in a read-only manner. Any other scenario in which user-data must be written to the database require isolated per-request context instances, otherwise other user´s data which is tracked by the same instance will be persisted as well. – Matze Mar 11 '15 at 11:19

2 Answers2

1

I think you should read a bit more about repository pattern for EntityFramework and UnitofWork pattern.

Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC

I know this is mvc and you are problably using web forms but you can get an idea of how to implement it.

Disposing the context on each request is a bit strange, because there might be requests where you will not touch the database, so you will be doing unnecessary code.

What you should do is get a layer for data access and implement a repository pattern that you will access on whatever method you will need on the code behind of your page.

Community
  • 1
  • 1
  • I actually don't use MVC but may change for it if it is better ! Many thanks for your answer that helped me, moreover you're right there are lots of requests where I don't modify the db so seems quite unnecessary. I don't know about Repository and Unit of Work but I will read it. Thanks again ! – Flash_Back Mar 11 '15 at 13:16
1

I would not rely on Thread.CurrentContext property.

Firstly, Microsoft says, Context class is not intended to be used directly from your code: https://msdn.microsoft.com/en-us/library/system.runtime.remoting.contexts.context%28v=vs.110%29.aspx

Secondly, imagine you want to make an async call to the database.

In this case an additional MyEntities instance will be constructed, and it will not be disposed in Application_EndRequest.

Furthermore, ASP.NET itself does not guarantee not to switch threads while executing a request. I had a similar question, have a look at this:

is thread switching possible during request processing?

I would use "MyDb_" + typeof(MyEntities).ToString() instead.

Disposing db context in Application_EndRequest is OK, but it produces a bit performance hit, 'cause your context will stay not disposed longer than needed, it is better to close it as soon as possible (you actually don't need an open context to render the page, right?)

Context pre request implementation would make sense if it has to be shared between different parts of your code, insted of creating a new instance each time.

For example, if you utilize the Repository pattern, and several repositories share the same db context while executing a request. Finally you call SaveChanges and all the changes made by different repositories are committed in a single transaction.

But in your example you are calling the database directly from your page's code, in this case I don't see any reason to not create a context directly with using.

Hope this helps.

Update: a sample with Context per request:

//Unit of works acts like a wrapper around DbContext
//Current unit of work is stored in the HttpContext
//HttpContext.Current calls are kept in one place, insted of calling it many times
public class UnitOfWork : IDisposable
{
    private const string _httpContextKey = "_unitOfWork";
    private MyContext _dbContext;

    public static UnitOfWork Current
    {
        get { return (UnitOfWork) HttpContext.Current.Items[_httpContextKey]; }
    }

    public UnitOfWork()
    {
        HttpContext.Current.Items[_httpContextKey] = this;
    }

    public MyEntities GetContext()
    {
        if(_dbContext == null)
            _dbContext = new MyEntities();

        return _dbContext;
    }

    public int Commit()
    {
        return _dbContext != null ? _dbContext.SaveChanges() : null;
    }

    public void Dispose()
    {
        if(_dbContext != null)
            _dbContext.Dispose();
    }
}

//ContextManager allows repositories to get an instance of DbContext
//This implementation grabs the instance from the current UnitOfWork
//If you want to look for it anywhere else you could write another implementation of IContextManager
public class ContextManager : IContextManager
{
    public MyEntities GetContext()
    {
        return UnitOfWork.Current.GetContext();
    }
}

//Repository provides CRUD operations with different entities
public class RepositoryBase
{
    //Repository asks the ContextManager for the context, does not create it itself
    protected readonly IContextManager _contextManager;

    public RepositoryBase()
    {
        _contextManager = new ContextManager(); //You could also use DI/ServiceLocator here
    }
}

//UsersRepository incapsulates Db operations related to User
public class UsersRepository : RepositoryBase
{
    public User Get(Guid id)
    {
        return _contextManager.GetContext().Users.Find(id);
    }

    //Repository just adds/updates/deletes entities, saving changes is not it's business
    public void Update(User user)
    {
        var ctx = _contextManager.GetContext();
        ctx.Users.Attach(user);
        ctx.Entry(user).State = EntityState.Modified;
    }
}

public class ItemsRepository : RepositoryBase
{
    public void UpdateSomeProperties(Item item)
    {
        var ctx = _contextManager.GetContext();
        ctx.Items.Attach(item);

        var entry = ctx.Entry(item);
        item.ModifiedDate = DateTime.Now;

        //Updating property1 and property2
        entry.Property(i => i.Property1).Modified = true;
        entry.Property(i => i.Property2).Modified = true;
        entry.Property(i => i.ModifiedDate).Modified = true;
    }
}

//Service encapsultes repositories that are necessary for request handling
//Its responsibility is to create and commit the entire UnitOfWork
public class AVeryCoolService
{
    private UsersRepository _usersRepository = new UsersRepository();
    private ItemsRepository _itemsRepository = new ItemsRepository();

    public int UpdateUserAndItem(User user, Item item)
    {
        using(var unitOfWork = new UnitOfWork()) //Here UnitOfWork.Current will be assigned
        {
            _usersRepository.Update(user);
            _itemsRepository.Update(user); //Item object will be updated with the same DbContext instance!

             return unitOfWork.Commit();
            //Disposing UnitOfWork: DbContext gets disposed immediately after it is not longer used.
            //Both User and Item updates will be saved in ome transaction
        }
    }
}

//And finally, the Page
public class AVeryCoolPage : System.Web.UI.Page
{
    private AVeryCoolService _coolService;

    protected void Btn_Click(object sender, EventArgs e)
    {
        var user = .... //somehow get User and Item objects, for example from control's values
        var item = ....

        _coolService.UpdateUserAndItem(user, item);
    }
}
Community
  • 1
  • 1
nativehr
  • 1,131
  • 6
  • 16
  • Thanks a lot for your answer, I understood many things thanks to it. Since I'm not using real MVC I don't see where I would call the database except from pages, so seems actually quite unnecessary to use context like that. I don't know about Repository pattern so I'll read but if I don't use it I'll keep going with "using" then :) – Flash_Back Mar 11 '15 at 13:27
  • Juste one more thing, if I just use "using", would it be good to keep the "context = DbContextManager.Current" in Page_Load anyway and then do "using(context)" in events and methods, or is "using(MyEntities context = new MyEntities())" better ? – Flash_Back Mar 11 '15 at 13:48
  • 1
    Simplified - imagine you utilize 2 classes during request processing - ClassA and ClassB, and both require to call the db. Instead both of them create its own Context instance, they look for it in `HttpContext`. Firstly, this will cause 1 open database connection insted of 2, that means it will increase performance, and secondly, changes made by both classes will be saved as *one* transaction, so you avoid the situation where changes from ClassA are written to the db, then something goes wrong and changes from ClassB are lost. – nativehr Mar 11 '15 at 14:01
  • 1
    For example, your page calls UserService to get a list of users and WaresService to get a list of wares, these services in turn look for `MyEntities` in the current `HttpContext`. But if you call db directly from the page I don't see any need to store the context at all - I would just write `using var db = new MyEntities()` where I need to fetch the data and close it immediately. – nativehr Mar 11 '15 at 14:07
  • Thanks a lot one more time. Your examples are simple and clear and I understand it well. I would just have a last question. Using services seems better, I actually have it and call it like that : "user = UserService.Get(guid)" in my Login's Page_Load (for example). Those services are looking for context via DbContextManager but there is still a need to get it from the page, am I wrong ? Otherwise, how could I call "SaveChanges()" after modifiying user in _Button_Click event ? In this case, should I call DbContextManager.Current in both page and services ? I can edit my first post if necessary. – Flash_Back Mar 11 '15 at 14:37
  • 1
    This is already another question - the entire design of your app. If you use services I'd recommend to do **all** the work related to the db in service - I mean both fetching and saving data. When you fetch users via a service, but save them directly from `Btn_Click` this looks like a design smell. If you call `service.GetUsers` in `Page_Load` why not calling `service.SaveUser` from `Btn_click`? In this case `Page` does not know about storing users at all, it does its work only - renders the markup. – nativehr Mar 11 '15 at 15:19
  • I think I've finally understood what I was missing from the beginning. Thanks so much for taking time to explain to me. I edited my first post with the potential final code, is it actually what you were thinking about, with minimal database acesses ? – Flash_Back Mar 11 '15 at 15:34
  • 1
    Yes, but I personally prefer to store a UnitOfWork per request, not DbContext. UnitOfWork acts like a wrapper of the db. Please see the updated answer, I shared some sample code with comments. – nativehr Mar 11 '15 at 16:41
  • Waow, thanks for all this, that's incredible ! I am not familiar with those notions at all but if I understood this well (please correct me if I'm wrong), there are as many repositories as my current services (with add, delete, update, get etc.). However if that's the case why is there an `UpdateSomeProperties()` method in `ItemsRepository` while it is in my Item class ? Regarding `AVeryCoolService`, I do understand the utility, but can't see what to put in it. Let's admit I also have a `Btn2_Click` in my page, will there be a `_coolService.SomeMethodSpecificToBtn2Click(...);` ? – Flash_Back Mar 11 '15 at 17:13
  • 1
    Nope, you usually have as many repositories, as entities. Service can encapsulate several repositpries (like CoolService encapsulates UserRepository and ItemRepository). But this is of course a subject to discuss, if your app is really simple, you probably can omit the services' layer... The code I shared is just the concept I use to utilize, but you can find many other implementations of Repository, UnitOfWork and ContextPerRequest. – nativehr Mar 11 '15 at 17:27
  • 1
    `UpdateSomeProperties` is in the repository,'cause it **knows how to persist** the item, while `Item` itself is a plain and simple object, that does not know how to persist itself in the db. So you can achieve separation of the responsibility:`Item` is plain class just to transfer data, repository is responsible for DbContext operations, Service's task is to open a unit of work, tell all repositories to do their work, then commit the unit of work. And yes, in this case there should be some method in the `Service` that is used by `Btn2`. Hope this helped you to clarify things :) – nativehr Mar 11 '15 at 17:34
  • Yes, got it all now ! I won't thank you again because I already did so much but you saved my app ;) I'm sorry that I can't do anything better than accepting your answer. I will probably ask another question to learn a little bit more about object oriented vs service oriented architecture for web apps but that's quite subjective I guess. – Flash_Back Mar 12 '15 at 09:58