NHibernate初学者指南(16):条件查询

非类型化的条件查询

我们从定义条件查询的根开始,代码如下所示:

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. 运行程序,结果如下图所示:

NHibernate初学者指南(16):条件查询_第1张图片

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. 再次运行程序,结果如下图所示:

NHibernate初学者指南(16):条件查询_第2张图片

注意,由于使用的是随机生成的数字,结果会不一样,我开始运行了两次,上图中的矩形框中都没有结果。

总结

首先我们讲解了条件查询的两种方式,即:非类型化的和强类型的。然后通过一个简单的例子,将理论知识得以应用,在例子中穿插着复习了前面的知识:使用Loquacious配置和ConfOrm映射。

你可能感兴趣的:(NHibernate初学者指南(16):条件查询)