实体框架内存使用与共享模型

时间:2012-03-03 18:13:10

标签: entity-framework memory metadata edmx

我有一个EF 4.2 EDMX模型,我在多租户应用程序中使用它。我连接到大约100个使用相同EDM模型的数据库。第一次访问每个数据库时,我的工作集上升了大约12Mb,这似乎主要由EDM元数据缓存占用。内存使用率永远不会下降。我认为元数据/查询缓存可以共享,因为它是相同的模型。

寻找减少内存占用的建议,但我怀疑我无法控制这一点。

注意:同样的场景不是CodeFirst的问题(我们也在使用它),但是我们有很多代码仍然使用EDMX模型,现在无法将其转换。

谢谢!

3 个答案:

答案 0 :(得分:5)

我相信你可以通过自己缓存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在处理DbContext时处理这个ObjectContext。

在SingleModelCachingObjectContext中,EF连接字符串用于创建MetadataWorkspace,并在创建后将其缓存在静态字段中。这是一个非常简单的缓存和带锁的简单线程安全 - 随意使它更适合您的应用程序需求。

获取MetadataWorkspace后,现在将解析EF连接字符串以获取存储连接字符串和提供程序。然后,这将用于创建正常的商店连接。

商店连接和缓存的MetadataWorkspace用于创建EntityConnection,然后创建将使用缓存的MetadataWorkspace而不是使用常规缓存机制的ObjectContext。

此ObjectContext用于支持DbContext。重写Dispose方法,以便存储连接不会泄漏。当处理DbContext时,它将处理ObjectContext,而ObjectContext又将调用Dispose,并且将处理商店连接。

除了确保它运行之外,我还没有真正测试过这个。知道它是否真的有助于你的内存使用问题,这将是非常有趣的。

答案 1 :(得分:0)

我对你的问题没有多少经验,但我的头脑中有一些建议:

您可以使用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)。

问候。

答案 2 :(得分:0)

我正在补充Arthur给出的答案。当我使用Arthur提出的解决方案时,我遇到了一个问题。我有几个存储过程映射到EF模型,当我执行它们时,它们失败了以下消息。

发现了System.InvalidOperationException   Message =“EntityCommand.CommandText的值对StoredProcedure命令无效.EntityCommand.CommandText值的格式必须为'ContainerName.FunctionImportName'。”   源= “System.Data.Entity的”

发生这种情况是因为在使用MetadataWorkspace初始化上下文时未设置DefaultContainerName。为了正常工作,我在下面进行了更改。

我使用稍微不同的方法来使用EFConnection而不是读取表单配置,因为在多租户DB的情况下,我们不会在配置中输入连接字符串并在运行时传递它。还使用了泛型,以便您可以在不同的上下文之间共享实现。还要更改上面的实现,以便仅在需要时(即设置工作空间时)锁定线程。

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