Ошибка CA2000 с оператором «using». Как сделать это действительным?

Следующий код дает мне эту ошибку анализа кода

CA2000: Microsoft.Reliability: в методе «SessionSummary.SessionSummary_Load (объект, EventArgs)» вызовите System.IDisposable.Dispose для объекта «сущности», прежде чем все ссылки на него будут вне области действия.

Я использую оператор «using», поэтому я удивлен этим:

    private void SessionSummary_Load(object sender, EventArgs e)
    {
        using (var entities = new DbEntities(Properties.Settings.Default.UserConnectionString))
        {
            entities.CommandTimeout = 7200;
            var sessions = from t in entities.TableName
                            where t.UserSession.Id == _id && t.Parent == 0
                            group t by new { t.UserSession, t.UserSession.SessionId } into sessionGroup
                            select new
                            {
                                Id = sessionGroup.Key.UserSession,                                   
                                Session = sessionGroup.Key.SessionId                                   
                            };

            summaryDataGridView.DataSource = sessions.Where(x => x.Time > 0.00);
            summaryDataGridView.Columns[4].DefaultCellStyle.Format = "N2";
            summaryDataGridView.Columns[4].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
        }
    }

person esac    schedule 01.09.2010    source источник


Ответы (2)


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

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

Опции:

  1. Оставьте все как есть. Если приведенное выше работает, то это вероятно потому, что удаление не влияет на состояние, необходимое для работы замыкания. Это рискованно. Делать ставку на «вероятно» — не лучшая идея, и со временем это может измениться. (Я могу вспомнить один случай, когда использование объекта после утилизации имеет смысл, но это неясно, и в любом случае это не то, что у вас есть).

  2. Принудительно выполнить запрос. Вызов ToList() или ToArray() в запросе запустит его и создаст результат в памяти, который затем будет использоваться в качестве источника данных. В лучшем случае это будет менее эффективно как в пространстве, так и во времени. В худшем случае это может быть ужасно (в зависимости от размера результатов, с которыми вы имеете дело).

  3. Убедитесь, что элемент управления завершает использование своего источника данных до выхода из области действия. Затем очистите источник данных. В зависимости от рассматриваемого элемента управления и некоторых других факторов (в частности, если он имеет явный метод DataBind()) сделать это может быть тривиально, невозможно или где-то посередине.

  4. Поместите сущность в переменную экземпляра. Реализовать IDisposable. В вашем методе Dispose() назовите его методом Dispose(). Не добавляйте финализатор для этого, так как вы только удаляете управляемый объект.

  5. Создайте перечисляемый метод, который обертывает запрос (и использование), а затем выполняет yield return для каждого элемента, возвращаемого запросом. Используйте это как источник данных.

5 кажется лучшим выбором для большинства случаев. Его преимущество заключается в том, что код сильно не меняется, но при этом не добавляются (потенциально большие, в зависимости от данных) накладные расходы числа 2. Обратите внимание, что просто вызов AsEnumerable (который почти влияет на порядок выполнения) не будет иметь такого же эффекта, так как закрытие по-прежнему оставит блок невыполненным.

Изменить: перечисляемое, обертывающее запрос, будет выглядеть так:

private IEnumerable GetSessions()
{
    using (var entities = new DbEntities(Properties.Settings.Default.UserConnectionString))
    {
        entities.CommandTimeout = 7200;
        var sessions = from t in entities.TableName
                        where t.UserSession.Id == _id && t.Parent == 0
                        group t by new { t.UserSession, t.UserSession.SessionId } into sessionGroup
                        select new
                        {
                            Id = sessionGroup.Key.UserSession,                                   
                            Session = sessionGroup.Key.SessionId                                   
                        };

        foreach(var sess in sessions.Where(x => x.Time > 0.00))
          yield return sess;
    }
}

Затем вы должны установить для изменения SessionSummary_Load значение:

private void SessionSummary_Load(object sender, EventArgs e)
{
        summaryDataGridView.DataSource = GetSessions();
        summaryDataGridView.Columns[4].DefaultCellStyle.Format = "N2";
        summaryDataGridView.Columns[4].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
    }
}

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

person Jon Hanna    schedule 02.09.2010
comment
Я не уверен, что понимаю, что вы подразумеваете под "перечислимым методом, который обертывает запрос и использование" - person esac; 03.09.2010
comment
@esac немного добавил к моему ответу, чтобы более подробно описать это. - person Jon Hanna; 03.09.2010
comment
Я попробую это в качестве эксперимента, меня беспокоит только то, что я не хочу создавать 100 различных методов для каждого запроса linq, который я делаю. - person esac; 03.09.2010
comment
Ну, это работает, но затем выдает исключение в следующей строке после установки DataSource... кажется, что Columns[4] теперь возвращает null. - person esac; 03.09.2010

Вы выполняете запрос в стиле LINQ по entities, но фактически не перечисляете результат в блоке using. Это создает проблему с закрытием, поскольку информация о запросе, хранящаяся в sessions.Where(x => x.Time > 0.00), хранится в summaryDataGridView.DataSource, и, таким образом, ссылка на entities остается в памяти после выхода из приведенного выше кода.

Это объясняется тем, что методы LINQ предоставляют deferrer выполнения*, что означает, что ни session, ни значение, которое вы присваиваете summaryDataGridView.DataSource, не будут оцениваться в приведенном выше коде.

Чтобы принудительно выполнить оценку, вы должны сделать следующее:

summaryDataGridView.DataSource = sessions.Where(x => x.Time > 0.00).ToList();

Добавление ToList() выше фактически приведет к выполнению вашего запроса и кешированию результатов в памяти. Кроме того, entities выйдет из области видимости, и у вас больше не будет ссылок на него через какое-либо замыкание; так что я полагаю, что это должно вам помочь.

*Примечание: это была первая ссылка, которую я нашел в поиске Google. Однако выглядело это неплохо.

person Dan Tao    schedule 02.09.2010
comment
Звучит как хорошая практика, но добавление ToList() не избавило от предупреждения CA. Любые идеи? - person esac; 02.09.2010
comment
@esac: интересно, получится ли свернуть запрос в один оператор? Попробуйте: var sessions = [ /*...what you have...*/ ].Where(x => x.Time > 0.00).ToList(); А затем просто summaryDataGridView.DataSource = sessions; Это работает? - person Dan Tao; 02.09.2010
comment
Нет, обновил, как вы сказали, и все равно получаю ту же ошибку. - person esac; 02.09.2010
comment
@esac: Это действительно странно. Вы заставили меня почесать голову сейчас. Я еще немного подумаю и вернусь к вам. Может быть, тем временем появится кто-нибудь еще, кто сможет объяснить, что здесь происходит. - person Dan Tao; 02.09.2010
comment
@esac: Вы пробовали какие-либо перечисляемые методы, которые я предложил? - person Jon Hanna; 03.09.2010