C# 实体框架包括较差的性能 上下文
我们似乎有一个实体框架6.x相关的问题。我们花了数周时间试图解决性能问题,并修复了我们能找到/想到的大部分问题。简而言之,当使用C# 实体框架包括较差的性能 上下文,c#,sql-server,entity-framework,query-performance,C#,Sql Server,Entity Framework,Query Performance,我们似乎有一个实体框架6.x相关的问题。我们花了数周时间试图解决性能问题,并修复了我们能找到/想到的大部分问题。简而言之,当使用Include时,我们看到性能大幅下降 利用EFCache 已启用运行EF 6.2的数据库模型缓存 利用缓存的视图 在可能的情况下,使用没有延迟加载的上下文,使用正确(且最小)的includes 对只读数据使用AsNoTracking 使用没有代理生成或自动检测更改的上下文(尽管后者似乎是一个最小的改进) 上下文生存期是最小的,在可能的情况下使用块进行单个查询 清理对
Include
时,我们看到性能大幅下降
- 利用EFCache
- 已启用运行EF 6.2的数据库模型缓存
- 利用缓存的视图
- 在可能的情况下,使用没有延迟加载的上下文,使用正确(且最小)的includes
- 对只读数据使用
AsNoTracking
- 使用没有代理生成或自动检测更改的上下文(尽管后者似乎是一个最小的改进)
- 上下文生存期是最小的,在可能的情况下使用块进行单个查询
- 清理对象上的构造函数,以便EF在将数据映射到对象时所经历的开销最小
- “一路异步”无疑提高了响应速度,但不会减少所做的工作
- 主键,集群guid
- 每个类型层次结构的表,实体是其中的一部分,导致双重联接
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();
}