У меня есть веб-приложение, в котором я только начал использовать Entity Framework. Я читал руководства для начинающих и темы о преимуществах объектного контекста для каждого запроса для веб-приложений. Однако я не уверен, что мой контекст находится в нужном месте ...

Я нашел этот очень полезный пост (Контекст объекта Entity Framework на запрос в ASP.NET?) и использовал предложенный код:

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

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

Затем я использую его на своих страницах:

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

Я много читал, но это все еще меня немного смущает. Работает ли средство получения контекста в Page_Load? Нужно ли мне по-прежнему использовать «using», или можно будет избавиться от метода Global.asax?

Если я что-то путаю, мне очень жаль, и я был бы очень, очень признателен, если бы кто-нибудь помог мне понять, где это должно быть.

Большое спасибо !

Редактирует следующий ответ и комментарии nativehr:

Вот 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;
        }
    }
}

Страница :

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

И класс ItemService:

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();
        }
    }
}
0
Flash_Back 11 Мар 2015 в 13:43

2 ответа

Лучший ответ

Я бы не стал полагаться на собственность Thread.CurrentContext.

Во-первых, Microsoft говорит, что класс Context не предназначен для использования непосредственно из вашего кода: https: //msdn.microsoft.com/en-us/library/system.runtime.remoting.contexts.context%28v=vs.110%29.aspx

Во-вторых, представьте, что вы хотите выполнить асинхронный вызов базы данных.

В этом случае будет создан дополнительный экземпляр MyEntities, который не будет размещен в Application_EndRequest.

Более того, сам ASP.NET не гарантирует отсутствие переключения потоков во время выполнения запроса. У меня был аналогичный вопрос, посмотрите на это:

возможно ли переключение потоков во время обработки запроса?

Вместо этого я бы использовал "MyDb_" + typeof(MyEntities).ToString().

Удаление контекста db в Application_EndRequest - это нормально, но это немного снижает производительность, потому что ваш контекст не будет удален дольше, чем необходимо, лучше закрыть его как можно скорее (на самом деле вам не нужен открытый контекст для рендеринга страницы, верно?)

Реализация предварительного запроса контекста имела бы смысл, если бы он был общим между различными частями вашего кода, чтобы каждый раз создавать новый экземпляр.

Например, если вы используете шаблон репозитория, и несколько репозиториев используют один и тот же контекст базы данных при выполнении запроса. Наконец, вы вызываете SaveChanges, и все изменения, внесенные в разные репозитории, фиксируются в одной транзакции.

Но в вашем примере вы вызываете базу данных непосредственно из кода своей страницы, в этом случае я не вижу причин не создавать контекст напрямую с помощью using.

Надеюсь это поможет.

Обновление : образец с контекстом для каждого запроса:

//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);
    }
}
1
Community 23 Май 2017 в 10:28

Я думаю, вам следует прочитать немного больше о шаблоне репозитория для EntityFramework и паттерне UnitofWork.

Реализация репозитория и шаблонов единиц работы в ASP.NET MVC

Я знаю, что это mvc, и вы, вероятно, используете веб-формы, но вы можете понять, как это реализовать.

Размещение контекста для каждого запроса немного странно, потому что могут быть запросы, в которых вы не будете касаться базы данных, поэтому вы будете выполнять ненужный код.

Что вам нужно сделать, так это получить слой для доступа к данным и реализовать шаблон репозитория, к которому вы будете обращаться любым методом, который вам понадобится в коде вашей страницы.

1
Community 23 Май 2017 в 10:28