【问题标题】:EntityFramework6 memory usage with large amount of table due to InitializedDatabases list由于 InitializedDatabases 列表,EntityFramework6 内存使用量很大
【发布时间】:2020-01-17 20:48:33
【问题描述】:

在我们的应用程序中有大量表(大约 50k) - 所有这些表都实际使用,这导致实体框架中的内存消耗很高。

在进行一些内存分析后,我注意到 DbCompiledModel 类被保存在内存中,因此在进行了一些搜索后,将其追踪到保留“InitializedDatabases”列表的 LazyInternalContext 类。

https://github.com/dotnet/ef6/blob/master/src/EntityFramework/Internal/LazyInternalContext.cs#L670

有没有办法阻止实体框架这样做?如果这就是“InitializeDatabaseAction”所暗示的,这不是代码优先设置,数据库设置和迁移不会在这个应用程序中完成。

设置“return null”或将“InitializerDisabled”设置为 true 可使一切正常,但宁愿不运行自定义实体构建,而且不知道仅“更改”源会产生什么影响。

大多数表具有相同的定义,因此也尝试了我在这里找到的解决方案: Change table name at runtime

尝试此操作时出现错误“此命令存在开放数据读取器”,此处不支持使用 postgres 和 MARS(不知道为什么需要它,这只会更改正在运行的 sql)

【问题讨论】:

  • 尝试将AsNoTracking 添加到您的查询中。这可以防止实体被缓存。但这只是只读实体的解决方案。 EF 希望注册对每个创建的实体的任何更改,因此它具有维护缓存和副本。通过存储原始对象并将其与-可能被修改的-对象进行比较来确定更改。它需要原始来创建 UPDATE 命令。
  • 你的内容生命周期太长了......但如果没有你的实现看起来很难说,但我的猜测是你在你的应用程序的生命周期中持有一个 dbcontext 实例,而不是而不是一个工作单元。
  • @Holger,这无济于事,因为问题不是实体。这是模型缓存。
  • @Seabizkit,每次都会处理上下文。该集合是静态的(参见 LazyInternalContext 的链接)
  • @Crazy 谢谢,这是否与道歉有关,并且会删除请包括您正在使用的 EF 版本:code-first-startup-performance 查看有关使用使用缓存数据库模型存储的链接,从来没有听说过 LazyInternalContext 上面的这个问题...entityframework.net/why-first-query-slowfusonic.net/developers/2014/07/09/…

标签: c# entity-framework


【解决方案1】:

解决方案在 Ivan Stoev 的评论中给出并且有效。

不使用反射无法关闭此功能,将“InternalContext.InitializerDisabled”属性设置为 true 会跳过字典。

所以:

  • 使用提供 DbCachedModel 的 DbContext 构造函数
  • 使用 Database.SetInitializer(null);
  • 使用反射设置 InternalContext.InitializerDisabled = true

我用来测试此示例的代码,作为测试设置,我有 1 个具有 30k 分区的主表,分区本身被查询,因为 postgres(特别是 9.x)不能很好地扩展大量分区:

    public class PartContext : DbContext {
        private static readonly string _ConnectionString = new NpgsqlConnectionStringBuilder {
            Host = "localhost",
            Port = 5432,
            Database = "postgres",
            Username = "postgres",
            Password = "password"
        }.ConnectionString;

        public readonly string Table;
        public readonly string Partition;

        public PartContext(string pMainTable, string pPartition) : base(
            new NpgsqlConnection() { ConnectionString = _ConnectionString },
            PartDbModelBuilder.Get(_ConnectionString, pPartition),
            true
        ) {
            Table = pMainTable;
            Partition = pPartition;

            Database.SetInitializer<PartContext>(null);


            /**
             * Disable database initialization so that the DbCachedModels are not kept internally in Entity
             * This causes high memory usage when having a lot of tables 
             * In EF 6.4.2 there was no way to 'manage' that Dictionary externally
             */
            try {
                var InternalContext = typeof(PartContext).BaseType.GetProperty("InternalContext", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(this, null);
                InternalContext.GetType().GetProperty("InitializerDisabled").SetValue(InternalContext, true);
            } catch(Exception) { }
        }

        public DbSet<MyPart> Parts { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder) {
            modelBuilder.HasDefaultSchema("public");
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        }
    }

这提供了 DbCachedModels:

我建议添加一些自定义缓存代码等,这只是一个示例

    class PartDbModelBuilder {
        public static DbCompiledModel Get(string pConnectionString, string pTable) {
            DbModelBuilder builder = new DbModelBuilder();
            builder.Entity<MyPart>().ToTable(pTable, "public");
            using (var connection = new NpgsqlConnection() { ConnectionString = pConnectionString }) {
                var obj = builder.Build(connection).Compile();
                return obj;
            }
        }
    }

这是我用作测试的实体:

    public class MyPart {
        public int id { get; set; }
        public string name { get; set; }
        public string value { get; set; }
    }

我用来运行测试的类:

    class EFTest {
        public void Run(int tableCount) {
            int done = 0;
            Parallel.For(0, tableCount, new ParallelOptions { MaxDegreeOfParallelism = 5 }, (i) => {
                string id = i.ToString().PadLeft(5, '0');
                using (var context = new PartContext("mypart", "mypart_" + id)) {
                    var objResult = context.Parts.First();
                    Console.WriteLine(objResult.name);
                }
                done++;
                Console.WriteLine(done + " DONE");
            });
        }
    }

表定义:

    CREATE TABLE IF NOT EXISTS mypart (
        id SERIAL,
        name text,
        value text
    ) partition by list (name);

    CREATE TABLE IF NOT EXISTS part partition of mypart_00000 for values in ('mypart00000');
    CREATE TABLE IF NOT EXISTS part partition of mypart_00001 for values in ('mypart00001');
    CREATE TABLE IF NOT EXISTS part partition of mypart_00002 for values in ('mypart00002');
    ...

Postgres 9:

    CREATE TABLE IF NOT EXISTS mypart (
        id SERIAL,
        name text,
        value text
    );

    CREATE TABLE IF NOT EXISTS ".$name."( CHECK ( name =  'mypart00000')) INHERITS (mypart);
    CREATE TABLE IF NOT EXISTS ".$name."( CHECK ( name =  'mypart00001')) INHERITS (mypart);
    CREATE TABLE IF NOT EXISTS ".$name."( CHECK ( name =  'mypart00002')) INHERITS (mypart);
    ...

【讨论】:

  • 你在做什么用`Table = pMainTable;分区 = pPartition; ` 似乎您正在以一种惊人的方式或非常不正确的方式使用 EF,这让您的生活变得比需要的更艰难。
  • 如果你创建分区,那么通常只使用 Ef db 不只是处理分区数据似乎在顶部开始指定它在内部执行的操作,基于分区值,也就是名称。
  • @Seabizkit Postgres 不能很好地扩展分区 - 我说的是每个设置在 10 到 30k 个分区之间 - 而且只会增长。我无法控制数据库,所以我坚持使用它。 Postgres 11 比我使用的 9.5 好得多,但是在主表上查询仍然是一个非常糟糕的主意。
  • 嗯,我猜我对此知之甚少。但我看到你把它复杂化了0利益。将其简化为基础 1 查询 查询是什么样的,它与 Ef 生成的有什么不同。是的,我知道您正在并行做事,但实际上并没有太大变化,您仍然可以在不使事情过于复杂的情况下完成所有这些工作....我明白他们为什么从顶部看不到附加值...一切你已经完成了可以用香草完成
  • 是修改连接字符串以包含分区,还是在查询中附加一些内容作为分区提示。我可以看到您传递值,但最终结果是什么。
猜你喜欢
  • 2014-04-06
  • 2021-09-30
  • 1970-01-01
  • 2022-07-15
  • 2021-11-16
  • 2018-06-12
  • 1970-01-01
  • 2016-04-15
  • 1970-01-01
相关资源
最近更新 更多