Использование памяти Entity Framework с общей моделью - PullRequest
1 голос
/ 03 марта 2012

У меня есть модель EF 4.2 EDMX, которую я использую в мультитенантном приложении.Я подключаюсь к примерно 100 базам данных, которые используют ту же модель EDM.При первом обращении к каждой базе данных мой рабочий набор увеличивается на ~ 12 МБ, что, по-видимому, в основном занято в кеше метаданных EDM.Использование памяти никогда не снижается.Я бы подумал, что кеш метаданных / запросов можно использовать совместно, поскольку это та же модель.

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

ПРИМЕЧАНИЕ. Этот же сценарий НЕ является проблемой с CodeFirst (который мы также используем), но у нас есть много кода, который все еще использует модель EDMX и не может преобразовать его прямо сейчас.

Спасибо!

Ответы [ 3 ]

5 голосов
/ 04 марта 2012

Я верю, что вы можете получить то, что хотите, кэшируя MetadataWorkspace самостоятельно.По сути, это то, что DbContext делает внутренне при использовании Code First.Это не так просто сделать, но я разработал быстрый прототип, который, по моему мнению, должен работать.

Основная идея здесь - позволить EF создать MetadataWorkspace один раз, а затем кэшировать его и использовать его явно каждый раз, когда вам нужносоздать экземпляр контекста.Это, очевидно, допустимо только в том случае, если каждый экземпляр контекста использует одну и ту же модель - т.е. один и тот же EDMX.Чтобы сделать эту работу, я создал производный ObjectContext, который обрабатывает кэширование:

public class SingleModelCachingObjectContext : ObjectContext
{
    private static readonly object WorkspaceLock = new object();
    private static MetadataWorkspace _workspace;

    public SingleModelCachingObjectContext(string connectionStringName)
        : base(CreateEntityConnection(connectionStringName))
    {
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            ((EntityConnection)Connection).StoreConnection.Dispose();
        }
    }

    private static EntityConnection CreateEntityConnection(string connectionStringName)
    {
        lock (WorkspaceLock)
        {
            if (_workspace == null)
            {
                _workspace = new EntityConnection("name=" + connectionStringName).GetMetadataWorkspace();
            }
        }

        var builder =
            new DbConnectionStringBuilder
            {
                ConnectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString
            };

        var storeConnection = DbProviderFactories.GetFactory((string)builder["provider"]).CreateConnection();
        storeConnection.ConnectionString = (string)builder["provider connection string"];

        return new EntityConnection(_workspace, storeConnection);
    }
}

Затем вы могли бы использовать это, создав конструктор в вашем классе DbContext следующим образом:

public MyDbContext(string connectionStringName)
    : base(new SingleModelCachingObjectContext(connectionStringName),
           dbContextOwnsObjectContext: true)
{
}

Этокак это устроено.Когда вы создаете экземпляр вашего DbContext, он, в свою очередь, создает экземпляр SingleModelCachingObjectContext, передавая имя строки подключения EF, которую вы хотите использовать.Он также говорит DbContext, что нужно избавиться от этого ObjectContext при удалении DbContext.

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

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

Соединение хранилища и кэшированное MetadataWorkspace используются для создания EntityConnection, а затем ObjectContext, который будет использовать кэшированное MetadataWorkspace вместо использования обычных механизмов кэширования.

Этот ObjectContext используется для поддержки DbContext.Метод Dispose переопределяется, поэтому соединение с хранилищем не протекает.Когда DbContext удаляется, он удаляет ObjectContext, который, в свою очередь, вызывает Dispose, и соединение с хранилищем будет удалено.

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

0 голосов
/ 13 марта 2013

Я добавляю ответ, данный Артуром.Я столкнулся с проблемой, когда использовал решение, представленное Артуром.У меня было несколько хранимых процедур, сопоставленных с моделью EF, и когда я выполнял их, они терпели неудачу со следующим сообщением.

System.InvalidOperationException был пойман Message = "Значение EntityCommand.CommandText недопустимо для команды StoredProcedure. Значение EntityCommand.CommandText должно иметь форму «ContainerName.FunctionImportName». "Source = "System.Data.Entity"

Это происходило из-за того, что DefaultContainerName не было задано при инициализации контекста с использованием MetadataWorkspace.Чтобы это работало правильно, я внес следующие изменения.

Я использовал немного другой подход, чтобы использовать EFConnection вместо чтения конфигурации формы, поскольку в случае мультитенантных БД мы не будем задавать строку подключения в конфигурации и передавать ее во время выполнения.Также используются обобщения, так что вы можете обмениваться реализациями между различными контекстами.Также измените вышеприведенную реализацию, чтобы блокировать поток только тогда, когда это необходимо, т.е. когда установлено рабочее пространство.

public class SingleModelCachingObjectContext<T> : ObjectContext
{
    private static readonly object WorkspaceLock = new object();
    private static MetadataWorkspace _workspace;

    public SingleModelCachingObjectContext(string connectionString)
        : base(CreateEntityConnection(connectionString))
    {
        DefaultContainerName = typeof (T).Name;
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            ((EntityConnection)Connection).StoreConnection.Dispose();
        }
    }

    protected static EntityConnection CreateEntityConnection(string connectionString)
    {
        if (_workspace == null)
        {
            lock (WorkspaceLock)
            {
                _workspace = new EntityConnection(connectionString).GetMetadataWorkspace();
            }
        }

        var builder =
            new DbConnectionStringBuilder
            {
                ConnectionString = connectionString
            };

        var storeConnection = DbProviderFactories.GetFactory((string)builder["provider"]).CreateConnection();
        storeConnection.ConnectionString = (string)builder["provider connection string"];
        return new EntityConnection(_workspace, storeConnection);
    }
}
0 голосов
/ 04 марта 2012

У меня не так много опыта с вашей проблемой, но есть несколько советов в верхней части головы:

Можете ли вы использовать ClearCache() метод?

Также попробуйте использовать:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
}

Обратите внимание, что если вы удалите таблицу EdmMetadata, то ничего не будет проверено, соответствует ли схема базы данных модели.

Попробуйте предварительно сгенерировать представления с использованием шаблона T4, такого как шаблон DbContext или шаблон создания Poco (http://blogs.msdn.com/b/adonet/archive/2010/01/25/walkthrough-poco-template-for-the-entity-framework.aspx).

С уважением.

...