【问题标题】:NHibernate query extremely slow compared to hard coded SQL query与硬编码的 SQL 查询相比,NHibernate 查询非常慢
【发布时间】:2014-01-24 23:05:09
【问题描述】:

我正在重写我的一些旧 NHibernate 代码,使其与数据库无关,并使用 NHibernate 查询而不是硬编码的 SELECT 语句或数据库视图。我被一个重写后速度非常慢的卡住了。 SQL查询是这样的:

 SELECT
    r.recipeingredientid AS id,
    r.ingredientid,
    r.recipeid,
    r.qty,
    r.unit,
    i.conversiontype,
    i.unitweight,
    f.unittype,
    f.formamount,
    f.formunit
   FROM recipeingredients r
   INNER JOIN shoppingingredients i USING (ingredientid)
   LEFT JOIN ingredientforms f USING (ingredientformid)

因此,这是一个非常基本的查询,其中包含从每个表中选择几列的几个 JOIN。此查询恰好返回大约 400,000 行,执行时间大约为 5 秒。我第一次尝试将其表达为 NHibernate 查询是这样的:

var timer = new System.Diagnostics.Stopwatch();
timer.Start();
var recIngs = session.QueryOver<Models.RecipeIngredients>()
   .Fetch(prop => prop.Ingredient).Eager()
   .Fetch(prop => prop.IngredientForm).Eager()
   .List();
timer.Stop();

此代码可以运行并生成所需的 SQL,但运行需要 120,264 毫秒。之后,我遍历recIngs 并填充List&lt;T&gt; 集合,这需要不到一秒钟的时间。所以,NHibernate 正在做的事情是非常慢!我有一种感觉,这只是为每一行构建模型类实例的开销。但是,就我而言,我只使用每个表中的几个属性,所以也许我可以优化它。

我尝试的第一件事是:

IngredientForms joinForm = null;
Ingredients joinIng = null;
var recIngs = session.QueryOver<Models.RecipeIngredients>()
   .JoinAlias(r => r.IngredientForm, () => joinForm)
   .JoinAlias(r => r.Ingredient, () => joinIng)
   .Select(r => joinForm.FormDisplayName)
   .List<String>();

在这里,我只从我的一个 JOIN'ed 表中获取一个值。 SQL 代码再次正确,这一次它抓取了 select 子句中的 FormDisplayName 列。此调用需要 2498 毫秒才能运行。我想我们正在做点什么!

但是,我当然需要返回几个不同的列,而不仅仅是一个。这就是事情变得棘手的地方。我的第一次尝试是匿名类型:

.Select(r => new { DisplayName = joinForm.FormDisplayName, IngName = joinIng.DisplayName })

理想情况下,这应该返回具有DisplayNameIngName 属性的匿名类型的集合。但是,这会在 NHibernate 中导致异常:

对象引用未设置为对象的实例。

另外,.List() 正在尝试返回 RecipeIngredients 的列表,而不是匿名类型。我也试过.List&lt;Object&gt;() 无济于事。唔。好吧,也许我可以创建一个新类型并返回这些类型的集合:

.Select(r => new TestType(r))

TestType 构造将采用 RecipeIngredients 对象并执行任何操作。但是,当我这样做时,NHibernate 会抛出以下异常:

发生了“NHibernate.MappingException”类型的未处理异常 在 NHibernate.dll 中

附加信息:没有持久性:KitchenPC.Modeler.TestType

我猜 NHibernate 想生成一个与 RecipeIngredients 的模式匹配的模型。

我该如何做我想做的事?似乎.Select() 只能用于选择单列的列表。有没有办法用它来选择多列?

也许一种方法是使用我的确切架构创建一个模型,但我认为这最终会和最初的尝试一样慢。

有没有什么方法可以从服务器返回这么多数据而无需大量开销,无需将 SQL 字符串硬编码到程序中或依赖于数据库中的 VIEW?我想让我的代码完全与数据库无关。谢谢!

【问题讨论】:

    标签: c# .net nhibernate fluent-nhibernate


    【解决方案1】:

    将选定列转换为人工对象(DTO)QueryOver语法有点不同。见这里:

    它的草稿可能是这样的,首先是 DTO

    public class TestTypeDTO // the DTO 
    {
        public string PropertyStr1 { get; set; }
        ...
        public int    PropertyNum1 { get; set; }
        ...
    }
    

    这是一个用法示例

    // DTO marker
    TestTypeDTO dto = null;
    
    // the query you need
    var recIngs = session.QueryOver<Models.RecipeIngredients>()
       .JoinAlias(r => r.IngredientForm, () => joinForm)
       .JoinAlias(r => r.Ingredient, () => joinIng)
    
        // place for projections
       .SelectList(list => list
         // this set is an example of string and int
         .Select(x => joinForm.FormDisplayName)
             .WithAlias(() => dto.PropertyStr1)  // this WithAlias is essential
         .Select(x => joinIng.Weight)            // it will help the below transformer
             .WithAlias(() => dto.PropertyNum1)) // with conversion
         ...
       .TransformUsing(Transformers.AliasToBean<TestTypeDTO>())
       .List<TestTypeDTO>();
    

    【讨论】:

    • 我明天试试这个!我相信它会起作用,因为您的答案总是如此。我需要你快速拨号!
    • 这行得通!我现在只添加了两个属性,但它运行时间为 7,225 毫秒。旧代码(带有IDataReader 的原始 SQL)耗时 6,497 毫秒。所以,它有点有点慢,但不是很多。希望添加其余属性不会进一步减慢速度。
    • 所以,一旦我添加了所有九个属性,它变得相当慢(大约 27 秒).. 但从好的方面来说,我正在挖掘源代码到 AliasToBeanTransformer 并且它发生在我身上我可以实现我自己的IResultTransformer 以使代码更清晰,所以效果很好。
    • 我必须再添加一条评论。力量,我不能祝福 NHibernate 的原因是:可扩展性。对自定义实现(自定义 Icriterion、IProjections、IResultTransformer...)的原生支持是无穷无尽的。虽然基本组真的非常非常大......如果需要,您可以简单地添加自定义组。与其他工具相比,这非常出色
    • 也许也看看这个stackoverflow.com/questions/10704462。我不会这样(因为我正在尽我所能避免使用NHiberante返回超过数百条记录)但对你来说这可能是另一种方式。 (我的意思是session.CreateSQLQuery("sql")
    【解决方案2】:

    所以,我想出了我自己的解决方案,它在 Radim 的解决方案(使用带有 DTO 的 AliasToBean 转换器)和 Jake 的解决方案(涉及选择原始属性并将每一行转换为 object[] 的列表)之间有点混合元组。

    我的代码如下:

    var recIngs = session.QueryOver<Models.RecipeIngredients>()
       .JoinAlias(r => r.IngredientForm, () => joinForm)
       .JoinAlias(r => r.Ingredient, () => joinIng)
    
       .Select(
          p => joinIng.IngredientId,
          p => p.Recipe.RecipeId,
          p => p.Qty,
          p => p.Unit,
    
          p => joinIng.ConversionType,
          p => joinIng.UnitWeight,
    
          p => joinForm.UnitType,
          p => joinForm.FormAmount,
          p => joinForm.FormUnit)
       .TransformUsing(IngredientGraphTransformer.Create())
       .List<IngredientBinding>();
    

    然后我实现了一个名为IngredientGraphTransformer 的新类,它可以将object[] 数组转换为IngredientBinding 对象列表,这就是我最终对这个列表所做的事情。这正是AliasToBeanTransformer 的实现方式,只是它根据别名列表初始化了一个DTO。

    public class IngredientGraphTransformer : IResultTransformer
    {
       public static IngredientGraphTransformer Create()
       {
          return new IngredientGraphTransformer();
       }
    
       IngredientGraphTransformer()
       {
       }
    
       public IList TransformList(IList collection)
       {
          return collection;
       }
    
       public object TransformTuple(object[] tuple, string[] aliases)
       {
          Guid ingId = (Guid)tuple[0];
          Guid recipeId = (Guid)tuple[1];
          Single? qty = (Single?)tuple[2];
          Units usageUnit = (Units)tuple[3];
          UnitType convType = (UnitType)tuple[4];
          Int32 unitWeight = (int)tuple[5];
          Units rawUnit = Unit.GetDefaultUnitType(convType);
    
          // Do a bunch of logic based on the data above
    
          return new IngredientBinding
          {
             RecipeId = recipeId,
             IngredientId = ingId,
             Qty = qty,
             Unit = rawUnit
          };
       }
    }
    

    注意,这没有像执行原始 SQL 查询并使用 IDataReader 循环遍历结果一样快,但是它比加入所有各种模型并构建完整的数据集。

    【讨论】:

    • 迈克说真的,干得好。真的。我也有自己的变压器。事实上,我确实主要将预测用于任何列表视图......很难解释“新 NHiernate”用户如何使用,这里或那里的最佳方式是什么......如果他们正在询问特定问题。但是你一直在证明你真的知道你需要什么以及如何去做。惊人的。 NHibernate 位居榜首,山中之王。你就是证据!!!
    【解决方案3】:
    IngredientForms joinForm = null;
    Ingredients joinIng = null;
    var recIngs = session.QueryOver<Models.RecipeIngredients>()
       .JoinAlias(r => r.IngredientForm, () => joinForm)
       .JoinAlias(r => r.Ingredient, () => joinIng)
       .Select(r => r.column1, r => r.column2})
       .List<object[]>();
    

    这行得通吗?

    【讨论】:

    • +1 也适用于此,尽管它只会将列转换为对象数组。然而,这似乎正是Transformers.AliasToBean&lt;T&gt; 在幕后使用的东西。
    猜你喜欢
    • 2021-01-12
    • 1970-01-01
    • 1970-01-01
    • 2020-01-05
    • 1970-01-01
    • 2017-08-15
    • 2023-03-14
    • 2011-09-02
    • 1970-01-01
    相关资源
    最近更新 更多