【问题标题】:Entity Framework (6) Performance Optimisation adviceEntity Framework(六)性能优化建议
【发布时间】:2013-06-20 14:35:01
【问题描述】:

我的应用程序中有一个 ADO.Net 数据访问层,它使用基本的 ADO.Net 和 CRUD 存储过程(每个操作一个,例如 Select_myTable、Insert_myTable)。可以想象,在像我们这样的大型系统中,DA 层所需的 DB 对象数量非常多。

我一直在研究将层类重构为 EF POCO 类的可能性。我已经设法做到了,但是当我尝试进行性能测试时,它变得非常可怕。使用下面的类(创建对象,将 Key 设置为所需的值,调用 dataselect),100000 次数据加载只需要大约 47 秒(数据库中只有少数记录)。而 Stored Proc 方法大约需要 7 秒。

我正在寻找有关如何优化这一点的建议 - 需要注意的是,我无法更改该层的公开功能 - 只能更改它如何实现方法(即我不能将上下文所有权的责任传递给BO层)

谢谢

public class DAContext : DbContext
{
    public DAContext(DbConnection connection, DbTransaction trans)
        : base(connection, false)
    {
        this.Database.UseTransaction(trans);

    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        //Stop Pluralising the Object names for table names.
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

        //Set any property ending in "Key" as a key type.
        modelBuilder.Properties().Where(prop => prop.Name.ToLower().EndsWith("key")).Configure(config => config.IsKey());

    }

    public DbSet<MyTable> MyTable{ get; set; }
}

public class MyTable : DataAccessBase
{

    #region Properties

    public int MyTableKey { get; set; }

    public string Name { get; set; }
    public string Description { get; set; }
    public bool Active { get; set; }
    public int CreatedBy { get; set; }
    public DateTime CreatedDate { get; set; }
    public int ModifiedBy { get; set; }
    public DateTime ModifiedDate { get; set; }

    #endregion

    #region constructors

    public MyTable()
    {
        //Set Default Values.
        Active = true;
        Name = string.Empty;
        CreatedDate = DateTime.MinValue;
        ModifiedDate = DateTime.MinValue;
    }

    #endregion

    #region Methods

    public override void DataSelect(System.Data.SqlClient.SqlConnection connection, System.Data.SqlClient.SqlTransaction transaction)
    {
        using (DAContext ctxt = new DAContext(connection, transaction))
        {

            var limitquery = from C in ctxt.MyTable
                             select C;


            //TODO: Sort the Query
            limitquery = FilterQuery(limitquery);

            var limit = limitquery.FirstOrDefault();

            if (limit != null)
            {
                this.Name = limit.Name;
                this.Description = limit.Description;
                this.Active = limit.Active;
                this.CreatedBy = limit.CreatedBy;
                this.CreatedDate = limit.CreatedDate;
                this.ModifiedBy = limit.ModifiedBy;
                this.ModifiedDate = limit.ModifiedDate;
            }
            else
            {
                throw new ObjectNotFoundException(string.Format("No MyTable with the specified Key ({0}) exists", this.MyTableKey));
            }
        }
    }

    private IQueryable<MyTable1> FilterQuery(IQueryable<MyTable1> limitQuery)
    {
        if (MyTableKey > 0) limitQuery = limitQuery.Where(C => C.MyTableKey == MyTableKey);
        if (!string.IsNullOrEmpty(Name)) limitQuery = limitQuery.Where(C => C.Name == Name);
        if (!string.IsNullOrEmpty(Description)) limitQuery = limitQuery.Where(C => C.Description == Description);
        if (Active) limitQuery = limitQuery.Where(C => C.Active == true);
        if (CreatedBy > 0) limitQuery = limitQuery.Where(C => C.CreatedBy == CreatedBy);
        if (ModifiedBy > 0) limitQuery = limitQuery.Where(C => C.ModifiedBy == ModifiedBy);
        if (CreatedDate > DateTime.MinValue) limitQuery = limitQuery.Where(C => C.CreatedDate == CreatedDate);
        if (ModifiedDate > DateTime.MinValue) limitQuery = limitQuery.Where(C => C.ModifiedDate == ModifiedDate);

        return limitQuery;
    }

    #endregion


}

【问题讨论】:

  • 就像另一条评论一样,我在比较中添加了一个额外的查询类型:ADO.Net(使用 datareader - 在内存中构建查询而不使用存储过程)。时间是:POCO EF:47s; 存储过程:7s; ADO.Net:5 秒
  • 那么您是否查看过 EF 正在执行的任何查询?您可能无意中导致它使用低效查询。此外,如果不发布您的 ADO.NET/Stored Proc 代码,我们将无法判断您在每种情况下的处理方式是否不同。在这里,您正在旋转一个新的上下文 100,000 次。我通常会看到手动 DAL 重用连接。

标签: c# .net performance entity-framework entity-framework-6


【解决方案1】:

启用跟踪后,选择速度很慢。您绝对应该关闭跟踪并重新测量。

看看我的基准测试

http://netpl.blogspot.com/2013/05/yet-another-orm-micro-benchmark-part-23_15.html

【讨论】:

  • 感谢您的回复。我尝试将.AsNoTracking() 添加到var limitquery = from C in ctxt.CoverageLimit,虽然我确实看到性能提升到 46s (POCO) 和 7s (Stored Proc),但这并不是很重要。不幸的是,除非我可以使时间接近可比,否则我无法接受这种变化
  • 我一直专门使用 SqlConnection/SqlCommand/SqlDataReader 来实现高性能,我更喜欢编写自己的 TSQL 查询而不是使用 LINQ。我发现我可以通过将 TableAttribute 和 ColumnAttribute 添加到我的类及其属性中来简化代码而不牺牲性能,然后构造通用方法以直接从具有 List ReadEntities(SqlDataReader reader, T 类型,字符串 tableAlias = null) 或 List> ReadEntities(SqlDataReader reader, Type[] types, string[] tableAliases ).
【解决方案2】:

这可能只是一种预感,但是......在您的存储过程中,过滤器定义良好,SP 处于编译状态,执行计划不错。您的 EF 查询从头开始构建并在每次使用时重新编译。因此,现在的任务变成了设计一种在使用之间编译和保存 EF 查询的方法。一种方法是重写您的 FilterQuery 以不依赖流畅的条件方法链。每次您的参数集更改时,不要附加或不附加一个新条件,而是将其转换为一个,在满足条件时应用过滤器,或者在不满足条件时被 1.Equals(1) 之类的东西覆盖。这样,您的查询可以被编译并可供重复使用。支持的 SQL 看起来很时髦,但执行时间应该会有所改善。或者,您可以设计面向方面的编程方法,其中将根据参数值重新使用已编译的查询。如果我有时间,我会在 Code Project 上发布一个示例。

【讨论】:

  • EF6 不会在每次使用时重新编译。版本 5 之前就是这种情况。
  • 你确定@DarrellNorton 在上面的场景中 limitQuery 变量不会在每次使用时都重新编译吗?
  • @Derek:EF 将其保存在自己的内部缓存中,因此除非您关闭 AppDomain 并重新开始,否则它应该被缓存。很高兴被证明不是这样。
  • 这很有趣。变量 limitquery 在循环中定义。我认识到它从一个循环到另一个循环的“签名”将是相同的,因此从技术上讲,它可以使用多种方式进行识别,因此是“可缓存的”。如果 EF 这样做,那真是太酷了:)
猜你喜欢
  • 1970-01-01
  • 2020-06-12
  • 1970-01-01
  • 1970-01-01
  • 2020-05-07
  • 1970-01-01
  • 2014-09-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多