简体   繁体   English

由于 InitializedDatabases 列表,EntityFramework6 内存使用量很大

[英]EntityFramework6 memory usage with large amount of table due to InitializedDatabases list

In our application there are a large amount of tables (around 50k) - all of those tables are actually used and this results in a high memory consumption in entity framework.在我们的应用程序中有大量的表(大约 50k)——所有这些表都被实际使用,这导致实体框架中的内存消耗很高。

After some memory profiling I noticed that DbCompiledModel classes were being kept in memory so after some searching tracked it down to the LazyInternalContext class that keeps a list of "InitializedDatabases".在进行了一些内存分析之后,我注意到 DbCompiledModel 类被保存在内存中,所以经过一些搜索后,将其追踪到保留“InitializedDatabases”列表的 LazyInternalContext 类。

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

Is there a way to prevent entity framework from doing this?, it's not a code first setup, database setup and migration are not done in this app if that is what the "InitializeDatabaseAction" implies.有没有办法阻止实体框架这样做?,如果这是“InitializeDatabaseAction”所暗示的,它不是代码优先设置,数据库设置和迁移不在此应用程序中完成。

Setting a "return null" or setting "InitializerDisabled" to true makes everything work but would rather not run a custom entity build plus don't know what the impact would be to just 'change' the source.设置“返回空值”或将“InitializerDisabled”设置为 true 会使一切正常,但宁愿不运行自定义实体构建,而且不知道仅“更改”源会产生什么影响。

Most tables have the same definition so also tried the solution I found here: Change table name at runtime大多数表都有相同的定义,所以也尝试了我在这里找到的解决方案: 在运行时更改表名

When trying this I'm getting an error "An open data reader exists for this command", using postgres and MARS isn't supported there (no idea why I'd need it, this just changes the sql that's run)尝试此操作时,我收到错误消息“此命令存在开放数据读取器”,不支持使用 postgres 和 MARS(不知道为什么我需要它,这只会更改正在运行的 sql)

The solution was given in a comment bu Ivan Stoev and works.解决方案是在评论中给出的 bu Ivan Stoev 并且有效。

There is no way to turn this off without using reflection, setting the "InternalContext.InitializerDisabled" property to true will make this skip the dictionary.如果不使用反射,则无法关闭此功能,将“InternalContext.InitializerDisabled”属性设置为 true 将使其跳过字典。

So:所以:

  • Use a DbContext constructor that provides the DbCachedModel使用提供 DbCachedModel 的 DbContext 构造函数
  • Use Database.SetInitializer(null);使用 Database.SetInitializer(null);
  • Set InternalContext.InitializerDisabled = true using reflection使用反射设置 InternalContext.InitializerDisabled = true

Code from the sample I used to test this, as a test setup I had 1 main table with 30k partitions, the partitions themselves are queried because postgres (especialy 9.x) does not scale well with high number of partitions:我用来测试这个示例的代码,作为测试设置,我有 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>();
        }
    }

This provides the DbCachedModels:这提供了 DbCachedModels:

I recommend adding some custom caching code etc, this is just from a sample我建议添加一些自定义缓存代码等,这只是来自示例

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

This is the entity I used as a test:这是我用作测试的实体:

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

Class I used to run the test:我用来运行测试的类:

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

Table definition:表定义:

    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: 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);
    ...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM