【问题标题】:Loading instance of entity takes more than 1 second加载实体实例需要超过 1 秒
【发布时间】:2019-07-09 06:36:18
【问题描述】:

我在 EF 中遇到了一件有趣的事情。如果我们使用基础实体获取子实体,则加载实体需要更多时间。我的模型如下所示:

public abstract class BaseDocument
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

public abstract class ComplexDocument : BaseDocument
{
    public string AuthorName { get; set; }
}

public abstract class SimpleDocument : BaseDocument
{
    public int Level { get; set; }
}

public abstract class OfficeDocument : ComplexDocument
{
    public string OfficeName { get; set; }
}

public abstract class ClassDocument : SimpleDocument
{
    public string HeadName { get; set; }
}

public class WordDocument : OfficeDocument
{
    public int PagesCount { get; set; }
}

public class ExcelDocument : OfficeDocument
{
    public int SheetsCount { get; set; }
}

public class TextDocument : ClassDocument
{
    public int LinesCount { get; set; }
}

我正在使用 TPT 方法。这是继承树 这是我的上下文类:

public class Context : DbContext
{
    public Context() : base(@"Server=(localdb)\MSSQLLocalDB;Database=EFSIX;Trusted_Connection=True;")
    {
        Database.CreateIfNotExists();
    }
    public DbSet<BaseDocument> BaseDocuments { get; set; }
    public DbSet<ComplexDocument> ComplexDocuments { get; set; }
    public DbSet<SimpleDocument> SimpleDocuments { get; set; }
    public DbSet<OfficeDocument> OfficeDocuments { get; set; }
    public DbSet<ClassDocument> ClassDocuments { get; set; }
    public DbSet<ExcelDocument> ExcelDocuments { get; set; }
    public DbSet<WordDocument> WordDocuments { get; set; }
    public DbSet<TextDocument> TextDocuments { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
       modelBuilder.Entity<BaseDocument>().ToTable("BaseDocuments");
       modelBuilder.Entity<ComplexDocument>().ToTable("ComplexDocuments");
       modelBuilder.Entity<SimpleDocument>().ToTable("SimpleDocuments");
       modelBuilder.Entity<OfficeDocument>().ToTable("OfficeDocuments");
       modelBuilder.Entity<ExcelDocument>().ToTable("ExcelDocuments");
       modelBuilder.Entity<WordDocument>().ToTable("WordDocuments");
       modelBuilder.Entity<ClassDocument>().ToTable("ClassDocuments");
       modelBuilder.Entity<TextDocument>().ToTable("TextDocuments");
    }
    public IQueryable<T> GetEntities<T>() where T : class
    {
        return Set<T>();
    }
}

我正在创建一些数据:

static void CreateTestData()
    {
        using (Context context = new Context())
        {
            for (int i = 0; i < 20; i++)
            {
                ExcelDocument excel = new ExcelDocument()
                {
                    Id = Guid.NewGuid(),
                    AuthorName = $"ExcelAuthor{i}",
                    Name = $"Excel{i}",
                    OfficeName = $"ExcelOffice{i}",
                    SheetsCount = (i + 1) * 10
                };
                context.ExcelDocuments.Add(excel);

                WordDocument word = new WordDocument()
                {
                    Id = Guid.NewGuid(),
                    AuthorName = $"WordAuthor{i}",
                    Name = $"Word{i}",
                    OfficeName = $"WordOffice{i}",
                    PagesCount = (i + 2) * 10
                };
                context.WordDocuments.Add(word);

                TextDocument text = new TextDocument()
                {
                    Id = Guid.NewGuid(),
                    Name = $"Text{i}",
                    LinesCount = (i + 3) * 10,
                    HeadName = $"Head{i}",
                    Level = i + 5
                };
                context.TextDocuments.Add(text);
            }
            context.SaveChanges();
        }
    }

我做了两种从数据库获取WordDocument 的方法。其中一个使用BaseDocument,另一个使用WordDocument。两者都返回 20 个 WordDocument 实例:

 static long ReadBaseDoc()
    {
        using (Context context = new Context())
        {
            var words= context.GetEntities<BaseDocument>().Where(e => e.Name.StartsWith("Word"));
            Stopwatch stopwatch = Stopwatch.StartNew();
            var instacnes = excel.ToList();
            stopwatch.Stop();
            return stopwatch.ElapsedMilliseconds;
        }
    }
    static long ReadWordDoc()
    {
        using (Context context = new Context())
        {
            var words = context.GetEntities<WordDocument>().Where(e => e.Name.StartsWith("Word"));
            Stopwatch stopwatch = Stopwatch.StartNew();
            var instacnes = words.ToList();
            stopwatch.Stop();
            return stopwatch.ElapsedMilliseconds;
        }
    }

我分别测试了moth方法,几次,平均方法ReadWordDoc需要25ms,方法ReadBaseDoc需要52ms(实例相同)。 现在问题不是太大,但是当我们有复杂的继承时,它需要超过 1 秒。我创建了 10 个类并继承自 BaseDocument。之后我执行了ReadBaseDocReadWordDoc 方法。 ReadWordDoc 用了 25 毫秒,ReadBaseDoc 用了 1023 毫秒。实例相同,为什么ReadBaseDoc 需要更多时间?在 EF 中避免此类问题的更好方法是什么?

【问题讨论】:

  • afaik,EF 中的主要性能问题与连接建立、模型创建、查询计算和物化有关,其中前两个将在特定上下文类的第一次调用时发生,第三个与每个具有特定条件的第一个(子)查询,并且每次第四个。所以会有一个开销,但一旦你做了第一个查询,它就不应该非常重要。关于问题还有很多要提到的,但归结为这四点。
  • 您能否准确添加 GetEntities 并包含模型。尝试获取多少条记录
  • @Eldho,我添加了一些细节
  • 将您的测试放入一个循环中并重复 1000 次。这应该显示延迟是否是第一次启动的开销。
  • 使用TPT或TPH的继承类型。请包含这些信息和详细信息以重现该问题。

标签: c# performance entity-framework


【解决方案1】:

看看here。有一些方法可以让 EF 更快,但在那些复杂的场景中,ORM 只会产生比它解决的问题更多的问题。

在您的情况下,一种方法是尝试将继承更改为 TablePerType,也许它会快一点。

其他方法是找到慢速请求并为它们使用 Dapper - 它会快得多。

最后一种方法是创建一个带有实时缓存的存储库,将整个数据库加载到内存中并保持最新 - 这应该是应用程序中的一个单例。如果您有多个应用程序使用同一个数据库,则需要连接数据更改触发器。

一般来说,对于像您这样的缓慢(且相对简单)的查询,我会说使用 Dapper + AutoMapper。保持 EF 以便您的数据库与您的类保持同步,但不要依赖它进行查询。

如果你真的想坚持ORM,我认为你需要切换nHibernate。自己没有尝试过,但从我阅读的内容来看,它几乎在所有可能的方面都非常出色,包括性能和启动时间。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-06
    • 1970-01-01
    • 2012-02-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-24
    相关资源
    最近更新 更多