Избегайте статических переменных в ASP.NET

ASP.NET

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

Хороший пример статического метода в библиотеке .NET это метод int.Parse(string val). Этот метод читает строку и пытается вернуть целочисельное представление. Метод логически связан с классом int (я предполагаю класс string мог бы иметь экземплярный метод ToInt(), но это было бы накладно, так как пришлось бы добавить также методы ToFloat(), ToDecimal(), To…()) — но когда вы его запускаете, нет надобности иметь конкретный экземпляр класса int. Вы запускаете его чтобы создать новый экземпляр класса int.

В общем случае я  придерживаюсь похожей логики в бизнес слое моего веб приложения. Я пишу статический метод Add(param1,….,paramx), который возвращает экземпляр класса после того как он сохранен в базу данных. Я мог бы сделать это и по другому, но думаю результирующий код читается лучше:

User.Add(username,password);

Чем:

new User(username,password).Add();

Или хуже:

DataContext.Users.InsertOnSubmit(new User(username,password));
DataContext.SubmitChanges();

Проблема со статическими методами в вашей бизнесс логике в том, что часто вам необходим общий объект что бы общаться с базой данных: SqlConnection, TableAdapter, LINQ DataContext, и т.п. Конечно, я могу создать эти объекты локально внутри каждого статического метода, но это занимает много времени и трудно поддерживаемо. Вместо этого я хочу определить общее свойство (в бизнесс классе, или его родительском классе) которое использует ленивую инициализацию (lazy initialization) и возвращает экземпляр объекта когда он мне нужен. Проблема в том, что свойство тоже должно быть статическим, что бы статический метод имел к нему доступ.

Самой легкой реализацией этого является что то вроде:

private static ModelDataContext dataContext=null;
protected static ModelDataContext DataContext
{
    get
        {
            if(dataContext==null)
                dataContext = new ModelDataContext();
            return dataContext;
        }
}

Хитрость в том, что это веротно будет работать при разработке и тестировании, пока ваш веб сайт не получит некоторую нагрузку. После этого вы начнете наблюдать разнообразные странные проблемы, которые могут даже и не указывать на данный код как на источник проблемы.

Но почему возникает эта проблема? Это связано с тем как статические переменные ограничиваются в ASP.NET приложении. Большинство веб программистов думают о каждой странице в их приложении как о отдельной программе. Вам необходимо вручную передавать данные между страницами когда это не обходимо. Поэтому они ошибочно предполагают что статические переменные неким образом привязаны к веб запросу или странице в их приложении. Это совершенно не правильно.

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

Почему это важно? Потому что статические переменные не привязаны к какому то экземпляру какого то класса, они должны быть созданы в рамках всего приложения. Фактически, статические переменные в ASP.NET тоже самое что глобальные переменные про которые все ваши учителя по программированию вредупоеждали вас.

Это значит, что для свойства выше, каждый отдельный запрос/страница/пользователь вашего сайта будут повторно использовать первый созданный экземпляр созданного DataContext. Это плохо по нескольким причинам. LINQ DataContext кэширует некоторые данные и зменения вносимые вами — вы можете быстро истратить память если каждый экземпляр не уничтожается достаточно быстро. TableAdapter держит открытым SQLConnection для повторного использования — так что если вы используете достаточно классов TableAdapter, вы можете иметь достаточно разных статических переменных что бы связать все ваши соединения с базой данных. Так как запросы могут возникать одновременно, вы также можете получить достаточно блокировок/конкурирующих доступов к переменной. И так далее.

Что вы должны с этим делать? В моем случае, я
использую статические свойства ссылающиеся на коллекции, которые ограничены неким подходящим для моего хранилища кратко живущим объектом. например, System.Web.HttpContext.Current.Items:

protected static ModelDataContext DataContext
      {
            get
            {
                 if(System.Web.HttpContext.Current.Items["ModelDataContext"]==null)
                     System.Web.HttpContext.Current.Items["ModelDataContext"] = new ModelDataContext();
                 return (ModelDataContext)System.Web.HttpContext.Current.Items["ModelDataContext"];
             }
       }

В этом случае, каждый экземпляр DataContext будет автоматически уничтожен для каждого обращения к сайту и DataContext-ы никогда не будут использоваться совместно двумя пользователями которые обращаются к сайту одновременно. Вы также можете использовать коллекции такие как ViewState, Session, Cache, и прочее (и я пробовал кое что из этого). Для моих нужд, коллекция HttpContext.Items ограничивает мои объекты точно где я хочу что бы они были доступны и точно как долго они должны быть живы.

Источник: http://www.foliotek.com/devblog/avoid-static-variables-in-asp-net/

1 комментарий

  1. Олег:

    Очень, ОЧЕНЬ полезная информация. Сам столкнулся с этим. Теперь вот разгребаю. И симптомы такие как описаны, — пока тестируешь — всё хорошо. Но как только появляется нагрузка на сайт — начинаются странности.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>