非类型化的条件查询
我们从定义条件查询的根开始,代码如下所示:
var query = session.CreateCriteria<Product>();
CreateCriteria方法返回一个实现了ICriteria接口的对象。如果获取所有产品的列表,那么我们需要使用接口ICriteria的List<T>方法,如下面的代码所示:
var products = session.CreateCriteria<Product>().List<Product>();
List<Product>()方法返回IList<Product>。和LINQ to NHibernate相比,当List方法被调用时查询立即执行。
当然还有一个非泛型的List法国法定义在ICriteria接口上。这个方法返回一个IList类型的对象。
限制返回的记录数
限制查询返回的记录数,可以使用SetMaxResults方法。从数据库中获得前10个产品,代码如下:
var first10Products = session.CreateCriteria<Product>() .SetMaxResults(10) .List<Product>();
筛选记录集
如果筛选出下架的产品,代码如下所示:
var discontinuedProducts = session.CreateCriteria<Product>() .Add(Restrictions.Eq("Discontinued", true)) .List<Product>();
通过给查询添加一个或多个限制条件筛选就完成了。如果我们想获得所有需要再订购的在架产品列表,我们可以使用下面的代码:
var discontinuedProducts = session.CreateCriteria<Product>() .Add(Restrictions.Eq("Discontinued", false)) .Add(Restrictions.GeProperty("ReorderLevel", "UnitsOnStock")) .List<Product>();
尽管这样很灵活,但是和LINQ to NHibernate相比也是非常容易出错的。假如把UnitsOnStock写成了UnitSonStock,只有在运行时才会意识到这个错误。
另外,Restrictions这个静态类还有很多定义筛选条件的方便的方法。
映射记录集
现在让我们讨论如何映射记录集。这也称为投影。使用Criteria API没有使用LINQ方便,我们必须首先定义想要投影的字段。然后,还要定义一个transformer,它将这些值放入到所需的目标类型中,如下面的代码所示:
var productsLookup = session.CreateCriteria<Product>() .SetProjection(Projections.ProjectionList() .Add(Projections.Property("Id")) .Add(Projections.Property("Name")) ) .SetResultTransformer( new AliasToBeanResultTransformer(typeof(NameID))) .List<NameID>();
在上面的代码中,我们使用SetProjection方法定义映射。我们选择product的Id和Name属性,并将它们放入到NameID类型的对象中。类NameID定义如下:
public class NameID { public int Id { get; set; } public string Name { get; set; } }
我们使用AliasToBeanResultTransformer把查询结果转换为NameID对象的列表。注意目标对象的属性名称必须与投影属性的名称匹配。如果不匹配,ProjectionList有一个Add的重载方法,我们可以定义一个别名。这个别名与目标对象的名称相匹配。
排序结果集
排序结果集非常简单。我们只需添加另外一个条件,如下面的代码所示:
var sortedProducts = session.CreateCriteria<Product>() .AddOrder(Order.Asc("Name")) .List<Product>();
以相反的顺序排列产品列表,只需使用Order类的Desc方法。我们还可以根据多个属性排序,只需为每个字段添加一个排序条件即可。
分组记录集
分组在LINQ提供程序中是单独的方法,但是在criteria API中是投影的一部分。假设我们想根据Category分组产品和统计每个类别的产品数,我们可以使用下面的查询:
var productsGrouped = session.CreateCriteria<Product>() .SetProjection(Projections.ProjectionList() .Add(Projections.GroupProperty("Category")) .Add(Projections.RowCount(), "Num") ) .List();
如果必须根据用户的选择动态生成查询,criteria API是最合适的。除此之外,LINQ to NHibernate更具有可读性以及从长远来看更有可维护性。
强类型的条件查询
NHibernatet 3引入了一个新的功能就是可以使用强类型定义条件查询。为此,QueryOver<T>加入到了ISession接口中。这里,泛型参数T表示我们想查询的实体类型。
使用QueryOver API,我们指定查询的根,如下面的代码所示:
var query = session.QueryOver<Product>();
简单的获取所有产品的列表,使用如下所示的查询:
var products = session.QueryOver<Product>().List();
与Criteria API相比,QueryOver不需要指定返回类型。
限制返回的记录数
限制查询返回的记录数,我们可以使用Take方法。这个查询和LINQ to NHibernate的查询相似,如下面所示:
var first10Products = session.QueryOver<Product>() .Take(10) .List();
筛选记录集
筛选记录集使用Where方法。获得所有下架产品的列表,使用下面的代码:
var discontinuedProducts = session.QueryOver<Product>() .Where(p => p.Discontinued) .List();
当然,我们还可以组合多个筛选,例如,获取所有在架上且需要再次订购的产品列表,如下面的代码所示:
var productsToReorder = session.QueryOver<Product>() .Where(p => p.Discontinued == false) .Where(p => p.ReorderLevel >= p.UnitsOnStock) .List();
我们还可以使用下面的方式筛选:
var productsToReorder = session.QueryOver<Product>() .Where(p => p.Discontinued == false &&p.ReorderLevel >= p.UnitsOnStock) .List();
排序结果集
使用QueryOver API排序和LINQ非常相似,只是LINQ定义OrderBy和OrderByDescending来定义升序和降序,而QueryOver API只定义了一个OrderBy方法。然而,这个方法必须和Asc或者Desc组合使用。当根据多个字段排序时,这两个API都有ThenBy(LINQ还有一个ThenByDescending)方法。
获取根据Name升序和UnitPrice降序排列的产品列表,代码如下所示:
var sortedProducts = session.QueryOver<Product>() .OrderBy(p => p.Name).Asc .ThenBy(p => p.UnitPrice).Desc .List();
投影结果集
使用QueryOver API定义映射也是最难的一部分。如果只想检索所有产品的Id和Name,并将它们填充到NameID对象的数组中,可以使用下面的代码完成:
var productsLookup = session.QueryOver<Product>() .Select(p => p.Id, p => p.Name) .TransformUsing(Transformers.AliasToBean<NameID>()) .List<NameID>();
注意我们是如何使用Select方法定义我们想投影的属性列表。每个属性都由一个拉姆达表达式定义,例如p=>p.Name投影Name属性。然后,我们使用TransformUsing方法声明NHibernate如何转换投影结果。在前面的例子中我们选择AliasToBean转换器声明NameID作为目标转换类型。NHibernate还定义了其他的转换器,甚至可以定义自己的转换器。静态类Transformers给出了我们可用转换器的列表。最后调用List<NameID>结束。这里我们声明目标类型,否则,NHibernate会认为目标类型仍然是Product。
分组记录集
当我们使用投影转换数据时,还可以分组记录集以及给字段应用聚合函数。按照Category分组所有的产品,然后统计每个类别下产品的个数,还可以计算每个类别下的平均单价以及每个类别下的库存量总和。如下面的代码所示:
var productsGrouped = session.QueryOver<Product>() .Select(Projections.Group<Product>(p => p.Category), Projections.Avg<Product>(p => p.UnitPrice), Projections.Sum<Product>(p => p.UnitsOnStock), Projections.RowCount()) .List<object[]>();
为了简单,上面的代码中我们没有定义转换,只是让NHibernate返回结果集的行数。
使用QueryOver检索数据
在这个例子中,我们添加一些产品到数据库,然后使用QueryOver方法检索这些产品。
同时,我们使用Loquacious配置和ConfOrm映射,就当复习前面的内容了。
ConfOrm映射在NHibernate初学者指南(6):映射模型到数据库之方式二
Loquacious配置在NHibernate初学者指南(14):配置的三种方式
下面正式开始我们的例子。
1. 在SSMS中创建一个数据库:QueryOverSample。
2. 在Visual Studio中创建一个控制台应用程序:QueryOverSample。
3. 为项目添加对NHibernate.dll,NHibernate.ByteCode.Castle.dll和ConfOrm.dll程序集的引用。
4. 在项目中添加一个类文件Category.cs,添加如下代码定义Category实体:
public class Category { public virtual Guid Id { get; set; } public virtual string Name { get; set; } public virtual string Description { get; set; } }
5. 在项目中添加一个类文件Product.cs,添加如下代码定义Product实体:
public class Product { public virtual Guid Id { get; set; } public virtual string Name { get; set; } public virtual Category Category { get; set; } public virtual decimal UnitPrice { get; set; } public virtual bool Discontinued { get; set; } public virtual int ReorderLevel { get; set; } public virtual int UnitsOnStock { get; set; } }
6. 在Program类中,添加一个静态方法使用Loquacious配置创建一个NHibernate的Configuration对象:
private static Configuration GetConfiguration() { var cfg = new Configuration(); cfg.SessionFactory() .Proxy .Through<ProxyFactoryFactory>() .Integrate .LogSqlInConsole() .Using<MsSql2008Dialect>() .Connected .Through<DriverConnectionProvider>() .By<SqlClientDriver>() .Using(new SqlConnectionStringBuilder { DataSource = @".", InitialCatalog = "QueryOverSample", IntegratedSecurity = true }); return cfg; }
7. 在Program类中添加一个静态方法为实体定义映射,代码如下所示:
private static void AddMappings(Configuration configuration) { var types = new[] { typeof(Category), typeof(Product) }; var orm = new ObjectRelationalMapper(); orm.TablePerClass(types); var mapper = new Mapper(orm); var hbmMappings = mapper.CompileMappingFor(types); configuration.AddDeserializedMapping(hbmMappings, "MyDomain"); }
8. 在Program类中添加一个静态方法创建或重新创建数据库架构,如下所示:
private static void BuildSchema(Configuration configuration) { new SchemaExport(configuration).Execute(true, true, false); }
9. 在Program中添加另外一个静态方法创建数据,如下面的代码所示:
private static void BuildSchema(Configuration configuration) { new SchemaExport(configuration).Execute(true, true, false); } private static void AddProductsAndCategories(ISessionFactory sessionFactory) { var categories = new List<Category>(); var products = new List<Product>(); var random = new Random((int)DateTime.Now.Ticks); for (var i = 1; i <= 5; i++) { var category = new Category { Name = string.Format("Category {0}", i) }; categories.Add(category); var count = random.Next(10); for (var j = 1; j <= count; j++) { var product = new Product { Name = string.Format("Product {0}", i * 10 + j), Category = category, UnitPrice = (decimal)random.NextDouble() * 10m, Discontinued = random.Next(10) > 8, UnitsOnStock = random.Next(100), ReorderLevel = random.Next(20) }; products.Add(product); } } using (var session = sessionFactory.OpenSession()) using (var tx = session.BeginTransaction()) { foreach (var category in categories) { session.Save(category); foreach (var product in products) { session.Save(product); } } tx.Commit(); } }
现在我们使用ISession接口的QueryOver方法创建一些数据报表。
10. 创建一个静态方法,创建一个session和transaction,用来调用报表创建报表方法,如下所示:
private static void PrintReports(ISessionFactory sessionFactory) { Console.WriteLine(); Console.WriteLine("---------------------"); Console.WriteLine("| Prining Reports |"); Console.WriteLine("---------------------"); using (var session = sessionFactory.OpenSession()) using (var tx = session.BeginTransaction()) { PrintListOfCategories(session); tx.Commit(); } }
11. 我们需要实现PrintListOfCategories方法,代码如下:
private static void PrintListOfCategories(ISession session) { Console.WriteLine("\r\nList of categories:\r\n"); var categories = session.QueryOver<Category>() .OrderBy(c => c.Name).Asc .List(); foreach (var category in categories) { Console.WriteLine("Category: {0}", category.Name); } }
12. 在Program类中,创建一个静态字段,如下所示:
private static ISessionFactory sessionFactory;
13. 在Main方法中添加如下代码,创建配置,添加映射,创建或重新创建数据库架构,创建session工厂,创建和存储category和product实体,最后调用PrintReports方法,如下所示:
var configuration = GetConfiguration(); AddMappings(configuration); BuildSchema(configuration); sessionFactory = configuration.BuildSessionFactory(); AddProductsAndCategories(sessionFactory); PrintReports(sessionFactory); Console.Write("\r\n\nHit enter to exit:"); Console.ReadLine();
14. 运行程序,结果如下图所示:
15. 添加另一个报表方法,检索没有下架和需要再次订购的所有产品列表。产品列表应该先按category名称排序,再按product名称排序,代码如下所示:
private static void PrintProductsToReorder(ISession session) { Console.WriteLine("\r\nList of products to reorder:\r\n"); Product productAlias = null; Category categoryAlias = null; var products = session.QueryOver<Product>(() => productAlias) .JoinAlias(() => productAlias.Category, () => categoryAlias) .Where(() => productAlias.Discontinued == false) .Where(() => productAlias.ReorderLevel >= productAlias.UnitsOnStock) .OrderBy(() => categoryAlias.Name).Asc .ThenBy(() => productAlias.Name).Asc .List(); Console.WriteLine(); foreach (var product in products) { Console.WriteLine( "Category: {0}, Product: {1} (Units on stock: {2})", product.Category.Name, product.Name, product.UnitsOnStock); } }
16. 在PrintReports方法中调用PrintProductsToReorder方法。
17. 再次运行程序,结果如下图所示:
注意,由于使用的是随机生成的数字,结果会不一样,我开始运行了两次,上图中的矩形框中都没有结果。
总结
首先我们讲解了条件查询的两种方式,即:非类型化的和强类型的。然后通过一个简单的例子,将理论知识得以应用,在例子中穿插着复习了前面的知识:使用Loquacious配置和ConfOrm映射。