我正在重写我的一些旧的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 })
理想情况下,这应该返回包含DisplayName
和IngName
属性的匿名类型的集合。但是,这会导致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
?我想让我的代码完全与数据库无关。谢谢!
答案 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[]>();
这会有用吗?