与硬编码的SQL查询相比,NHibernate查询速度极慢

时间:2014-01-24 23:05:10

标签: c# .net nhibernate fluent-nhibernate

我正在重写我的一些旧的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,264ms。之后,我遍历recIngs并填充List<T>集合,这需要不到一秒钟。所以,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表中获取一个值。 SQL代码再次正确,这次抓取select子句中的FormDisplayName列。此调用需要2498ms才能运行。我想我们正在做点什么!!

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

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

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

  

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

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

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

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

  

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

     

其他信息:没有持久性:KitchenPC.Modeler.TestType

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

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

也许有一种方法是使用我的确切模式创建模型,但我认为这最终会像原始模式一样慢。

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

3 个答案:

答案 0 :(得分:3)

将所选列转换为人工对象(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>();

答案 1 :(得分:2)

所以,我提出了自己的解决方案,这是Radim的解决方案(使用AliasToBean变换器和DTO之间的混合,以及Jake的解决方案,包括选择原始属性并将每一行转换为{ {1}}元组。

我的代码如下:

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的实现方式,只是根据别名列表初始化DTO。

AliasToBeanTransformer

注意,与执行原始SQL查询并使用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 }; } } 循环结果一样快,但是 比加入 更快所有各种模型和构建全套数据。

答案 2 :(得分:1)

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[]>();

这会有用吗?