C# 实体框架包括较差的性能 上下文

C# 实体框架包括较差的性能 上下文,c#,sql-server,entity-framework,query-performance,C#,Sql Server,Entity Framework,Query Performance,我们似乎有一个实体框架6.x相关的问题。我们花了数周时间试图解决性能问题,并修复了我们能找到/想到的大部分问题。简而言之,当使用Include时,我们看到性能大幅下降 利用EFCache 已启用运行EF 6.2的数据库模型缓存 利用缓存的视图 在可能的情况下,使用没有延迟加载的上下文,使用正确(且最小)的includes 对只读数据使用AsNoTracking 使用没有代理生成或自动检测更改的上下文(尽管后者似乎是一个最小的改进) 上下文生存期是最小的,在可能的情况下使用块进行单个查询 清理对

我们似乎有一个实体框架6.x相关的问题。我们花了数周时间试图解决性能问题,并修复了我们能找到/想到的大部分问题。简而言之,当使用
Include
时,我们看到性能大幅下降

  • 利用EFCache
  • 已启用运行EF 6.2的数据库模型缓存
  • 利用缓存的视图
  • 在可能的情况下,使用没有延迟加载的上下文,使用正确(且最小)的includes
  • 对只读数据使用
    AsNoTracking
  • 使用没有代理生成或自动检测更改的上下文(尽管后者似乎是一个最小的改进)
  • 上下文生存期是最小的,在可能的情况下使用块进行单个查询
  • 清理对象上的构造函数,以便EF在将数据映射到对象时所经历的开销最小
  • “一路异步”无疑提高了响应速度,但不会减少所做的工作
我们仍然存在可能产生影响的当前(假定)问题:

  • 主键,集群guid
  • 每个类型层次结构的表,实体是其中的一部分,导致双重联接
我们已经讨论过的大部分相关话题都没有太大影响。据我们所知,数据库“正常”。在查询进入数据库之前,我们利用log4net拦截器发现,尽管我们的一些包含3-7个include的查询非常庞大,但它们的速度并不是非常慢:时间从0毫秒到100毫秒不等。在对象“准备好”使用之前,它往往是2000毫秒到8000毫秒

我们的数据库中目前最多有50000个实体。然而,即使有一个近乎干净的数据库,也没有什么区别

代码 (简化、提取)模型结构:

public class Entity
{
    public virtual Guid Id { get; set; }
    public virtual long Version { get; set; }
    public virtual string EntityType { get; set; }
}

public class User : Entity
{
    public virtual Guid Id { get; set; }
    public virtual string Username { get; set; }
    public virtual string Password { get; set; }

    public virtual Person Person { get; set; }
}

public class Person : Entity
{
    public virtual Guid Id { get; set; }
    public virtual DateTime DateOfBirth { get; set; }
    public virtual string Name { get; set; }

    public virtual Employee Employee { get; set; }
}

public class Employee : Entity
{
    public virtual Guid Id { get; set; }
    public virtual string EmployeeCode { get; set; }
}
(简化的)慢速查询。通过包装秒表进行监视表示平均持续时间为2秒,但查询本身在log4net生成的日志文件中只列出了几毫秒:

var userId = .... // Obtained elsewhere
using (var context = new DbContext())
{
    var user =
        context.Set<User>()
            .Include(u => u.Person.Employee)
            .FirstOrDefault(u => u.Id == userId);
}
更新2 作为对@grek40的响应,我们现有的拦截器将添加到每个select查询中,以确保我们接收的实体没有标记
Deleted==true
。它为每个object+include连接Entities表,因此上面的查询显示了3个附加连接。如果我们禁用拦截器,剩下的将是4个连接,而不是7个。我们没有考虑太多,但是现在我们已经禁用了它,上面查询的计算时间,通过实体框架,从~3秒增加到~2秒。它似乎对我们看到的三分之一的性能问题负责

更新3 为了响应@GertArnold,下面是我们实体基类的映射代码,与上面的查询匹配:

modelBuilder.Entity<Entity>()
            .HasKey(p => new { p.Id })
            // Table Per Type (TPT) inheritance root class
            .ToTable("Entities", "dbo");
        // Properties:
        modelBuilder.Entity<Entity>()
            .Property(p => p.Id)
                .IsRequired()
                .HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None)
                .HasColumnType("uniqueidentifier");
        modelBuilder.Entity<Entity>()
            .Property(p => p.Version)
                .IsRequired()
                .IsConcurrencyToken()
                .HasColumnType("bigint");
        modelBuilder.Entity<Entity>()
            .Property(p => p.EntityType)
                .IsRequired()
                .HasColumnType("varchar");
        modelBuilder.Entity<Entity>()
            .Property(p => p.Deleted)
                .IsRequired()
                .HasColumnType("bit");
        modelBuilder.Entity<Entity>()
            .Property(p => p.UpdatedBy)
                .HasColumnType("uniqueidentifier");
        modelBuilder.Entity<Entity>()
            .Property(p => p.UpdatedAt)
                .HasColumnType("datetime");
        modelBuilder.Entity<Entity>()
            .Property(p => p.CreatedBy)
                .HasColumnType("uniqueidentifier");
        modelBuilder.Entity<Entity>()
            .Property(p => p.CreatedAt)
                .HasColumnType("datetime");
        modelBuilder.Entity<Entity>()
            .Property(p => p.LastRevision)
                .IsRequired()
                .HasColumnType("bigint");
        modelBuilder.Entity<Entity>()
            .Property(p => p.AccessControlListId)
                .HasColumnType("uniqueidentifier");
        modelBuilder.Entity<Entity>()
            .Property(p => p.EntityStatus)
                .IsRequired()
                .HasColumnType("bigint");
        modelBuilder.Entity<Entity>()
            .Property(p => p.CheckSum)
                .IsRequired()
                .HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Computed)
                .IsConcurrencyToken()
                .HasColumnType("int");
modelBuilder.Entity()
.HasKey(p=>new{p.Id})
//每类型表(TPT)继承根类
.ToTable(“实体”、“dbo”);
//特性:
modelBuilder.Entity()
.Property(p=>p.Id)
.IsRequired()
.HasDatabaseGenerateOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGenerateOption.None)
.HasColumnType(“唯一标识符”);
modelBuilder.Entity()
.Property(p=>p.Version)
.IsRequired()
.IsConcurrencyToken()
.HasColumnType(“bigint”);
modelBuilder.Entity()
.Property(p=>p.EntityType)
.IsRequired()
.HasColumnType(“varchar”);
modelBuilder.Entity()
.Property(p=>p.Deleted)
.IsRequired()
.HasColumnType(“bit”);
modelBuilder.Entity()
.Property(p=>p.updateBy)
.HasColumnType(“唯一标识符”);
modelBuilder.Entity()
.Property(p=>p.UpdatedAt)
.HasColumnType(“日期时间”);
modelBuilder.Entity()
.Property(p=>p.CreatedBy)
.HasColumnType(“唯一标识符”);
modelBuilder.Entity()
.Property(p=>p.CreatedAt)
.HasColumnType(“日期时间”);
modelBuilder.Entity()
.Property(p=>p.LastRevision)
.IsRequired()
.HasColumnType(“bigint”);
modelBuilder.Entity()
.Property(p=>p.AccessControlListId)
.HasColumnType(“唯一标识符”);
modelBuilder.Entity()
.Property(p=>p.EntityStatus)
.IsRequired()
.HasColumnType(“bigint”);
modelBuilder.Entity()
.Property(p=>p.CheckSum)
.IsRequired()
.HasDatabaseGenerateOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGenerateOption.Computed)
.IsConcurrencyToken()
.HasColumnType(“int”);

很可能是索引问题或表上缺少索引,请尝试在Microsoft SQL server tunning advisor上运行慢速查询,它将为您提供有关查询和表的建议


此链接将非常有用:

在我看来,对于获取用户信息这样简单的操作来说,查询太复杂(连接太多)

为了获得最大的性能,只需编写带有@userId参数的存储过程,在没有实体框架的存储过程中优化SQL查询(检查SSMS中的实际查询计划),然后只需在实体框架中编写包装器来调用此过程

如果还不够,则针对此查询

如果仍然不够,则必须重新设计数据库结构,使其更加简单,如果用户表或员工表发生更改,则可以在临时表中缓存一些视图,并通过触发器更新这些缓存视图。这会有很大帮助。

请尝试

var userId = .... // Obtained elsewhere
using (var context = new DbContext())
{
    var user =
        context.Set<User>()
            .Include(u => u.Person.Employee)
            .Where(u => u.Id == userId)
            .ToList()
            .FirstOrDefault();
}
var userId=..//从别处获得
使用(var context=new DbContext())
{
变量
modelBuilder.Entity<Entity>()
            .HasKey(p => new { p.Id })
            // Table Per Type (TPT) inheritance root class
            .ToTable("Entities", "dbo");
        // Properties:
        modelBuilder.Entity<Entity>()
            .Property(p => p.Id)
                .IsRequired()
                .HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None)
                .HasColumnType("uniqueidentifier");
        modelBuilder.Entity<Entity>()
            .Property(p => p.Version)
                .IsRequired()
                .IsConcurrencyToken()
                .HasColumnType("bigint");
        modelBuilder.Entity<Entity>()
            .Property(p => p.EntityType)
                .IsRequired()
                .HasColumnType("varchar");
        modelBuilder.Entity<Entity>()
            .Property(p => p.Deleted)
                .IsRequired()
                .HasColumnType("bit");
        modelBuilder.Entity<Entity>()
            .Property(p => p.UpdatedBy)
                .HasColumnType("uniqueidentifier");
        modelBuilder.Entity<Entity>()
            .Property(p => p.UpdatedAt)
                .HasColumnType("datetime");
        modelBuilder.Entity<Entity>()
            .Property(p => p.CreatedBy)
                .HasColumnType("uniqueidentifier");
        modelBuilder.Entity<Entity>()
            .Property(p => p.CreatedAt)
                .HasColumnType("datetime");
        modelBuilder.Entity<Entity>()
            .Property(p => p.LastRevision)
                .IsRequired()
                .HasColumnType("bigint");
        modelBuilder.Entity<Entity>()
            .Property(p => p.AccessControlListId)
                .HasColumnType("uniqueidentifier");
        modelBuilder.Entity<Entity>()
            .Property(p => p.EntityStatus)
                .IsRequired()
                .HasColumnType("bigint");
        modelBuilder.Entity<Entity>()
            .Property(p => p.CheckSum)
                .IsRequired()
                .HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Computed)
                .IsConcurrencyToken()
                .HasColumnType("int");
var userId = .... // Obtained elsewhere
using (var context = new DbContext())
{
    var user =
        context.Set<User>()
            .Include(u => u.Person.Employee)
            .Where(u => u.Id == userId)
            .ToList()
            .FirstOrDefault();
}