zoukankan      html  css  js  c++  java
  • NHibernate初学者指南(17):查询的其他知识点

    本篇包括以下几个知识点:

    • Hibernate查询语言(HQL)
    • 延迟加载属性
    • 批量执行多个查询
    • 预先加载和延迟加载比较
    • 批量数据修改

    Hibernate查询语言(HQL)

    HQL是NHibernate原始的查询语言,它和SQL很像,但是比其他更面向对象。HQL查询定义为字符串,所以不是类型安全的。另一方面,HQL支持动态实体。

    每个HQL查询都通过调用ISession接口的CreateQuery方法创建,HQL字符串作为参数。

    查询产品列表,如下面的代码所示:

    var products = session.CreateQuery("from Product p").List<Product>();

    限制查询返回的记录数,可以使用SetMaxResults,跳过一些记录,可以使用SetFirstResult方法。

    var first10Products = session.CreateQuery("from Product p")
                          .SetFirstResult(10)
                          .SetMaxResults(10)
                          .List<Product>();

    注意从NHibernate3.2开始,我们可以将上面的查询写成"from Product skip 10 take 10"。

    筛选产品列表,只检索断货的产品,代码如下所示:

    var discontinuedProducts = session
                              .CreateQuery("from Product p where p.Discontinued")
                              .List<Product>();

    也可以使用参数定义筛选,代码如下所示:

    var hql = "from Product p" +
              " where p.Category = :category" +
              " and p.UnitPrice <= :unitPrice";
    var cheapFruits = session
                    .CreateQuery(hql)
                    .SetString("category", "Fruits")
                    .SetDecimal("unitPrice", 1.0m)
                    .List<Product>();

    注意上面的代码是如何使用SetString和SetDecimal方法定义相应参数值的。SetString和SetDecimal方法的第一个参数是不带冒号的参数名称。

    如果想投影实体列表,可以使用前面提到的结果转换器,如下面的代码所示:

    var productsLookup = session
                        .CreateQuery("select Id as Id, Name as Name from Product")
                        .SetResultTransformer(Transformers.AliasToBean<NameID>())
                        .List<NameID>();

    注意,必须为每个列定义一个别名,即使别名和列相同。

    排序可以使用order by关键字,代码如下所示:

    var sortedProducts = session
                        .CreateQuery("from Product p order by p.Name, p.UnitPrice desc")
                        .List<Product>();

    如果想根据一个或多个字段分组记录集,可以使用group by关键字。所有出现在select部分且不是根据它分组的都要应用聚合函数,如下面的代码所示:

    var productsGrouped = session
                        .CreateQuery("select p.Category as Category," +
                        " count(*) as Count," +
                        " avg(p.UnitPrice) as AveragePrice" +
                        " from Product p" +
                        " group by p.Category")
                        .List();

    在上面的代码中,我们根据Category分组,并返回Category和每个category的记录个数以及每个category的平均单价。

    由于缺少转换,返回的结果集是IList。每个列表项都是一个object类型的数组。

    我们可以定义一个转换,使用LINQ to Object使返回的结果集对开发者更友好:

    var productsGrouped = session
                          .CreateQuery("select p.Category as Category," +
                            " count(*) as Count," +
                            " avg(p.UnitPrice) as AveragePrice" +
                            " from Product p" +
                            " group by p.Category")
                            .SetResultTransformer(Transformers.AliasToEntityMap)
                            .List<IDictionary>()
                            .Select(r => new
                            {
                                Category = r["Category"],
                                Count = r["Count"],
                                AveragePrice = r["AveragePrice"],
                             });

    AliasToEntityMap转换器转换结果集的每一行类型由object[]为IDictionary,key对应于查询列的别名。使用最后的Select语句,映射IDictionary为带有Category,Count和AveragePrice字段的匿名类型。

    延迟加载属性

    NHibernate 3的一个新功能就是可以延迟加载一个实体的特定属性。如果实体的某个属性内容非常大,例如图片,这个功能就派上用场了。大多数情况下,操作实体的时候并不需要总是访问这个属性的内容。默认情况下不加载该属性,只有需要时显示的访问它才更有意义。

    使用XML定义一个实体映射时,在property标签中有一个lazy特性,如下面的代码所示:

    <property name="SomeProperty" lazy="true" ="" />

    我们还可以使用Fluent NHibernate的fluent映射API定义延迟属性,如下所示:

    Map(x => x.SomeProperty).LazyLoad();

    让我们分析一下查询一个带有延迟属性Review的Book实体NHibernate会生成什么SQL语句。这里,我们定义一个简单的Book实体,如下面的代码所示:

    public class Book
    {
        public virtual int Id { get; set; }
        public virtual string BookTitle { get; set; }
        public virtual int YearOfPublication { get; set; }
        public virtual string Review { get; set; }
    }

    使用Fluent NHibernate映射实体,如下面的代码所示:

    public class BookMap : ClassMap<Book>
    {
        public BookMap()
        {
            Id(x => x.Id);
            Map(x => x.BookTitle);
            Map(x => x.YearOfPublication);
            Map(x => x.Review)
            .CustomType("StringClob")
            .LazyLoad();
        }
    }

    注意Review属性是如何映射的。我们使用自定义类型StringClob,使用SQL Server时,它会被转换为VARCHAR(MAX)列类型。我们还使用LazyLoad方法定义了Review是延迟属性。

    现在根据ID加载实体,NHibernate生成的查询如下图所示:

    注意Review列没有出现在上面的SELECT语句中。如果我们现在访问Review属性,那么NHibernate延迟加载这个属性,生成下面的SQL语句:

    注意,如果一个实体中有不止一个延迟属性,NHibernate会在第一次访问其中一个属性时加载所有的延迟属性。

    批量执行多个查询

    到目前为止,我们对数据库进行的每个查询都是往返的。有时候我们需要执行多个查询,为了提高程序的性能,可以批量发送所有的查询到数据库,,然后数据库发送查询结果集的列表给我们,而不是单个结果集。

    为此,LINQ to NHibernate提供程序定义了一个ToFuture扩展方法。当访问第一个查询时,由ToFuture终止的所有查询批量发送到数据库。假设我们的程序是订单系统,我们想一次加载类别列表,给定类别的活跃产品的列表,以及返回产品的数量。代码如下所示:

        using (var tx = session.BeginTransaction())
        {
            var categories = session.Query<Category>().ToFuture();
            var query = session.Query<Product>()
                        .Where(p => !p.Discontinued)
                        .Where(p => p.Category.Name == "Fruits");
            var products = query.ToFuture();
            var count = query.GroupBy(p => p.Discontinued)
                        .Select(x => x.Count())
                        .ToFutureValue();
            // get the results
            var result = new Result
            {
                Categories = categories.ToArray(),
                Products = products.ToArray(),
                ProductCount = count.Value
            };
        }
    }

    由NHibernate生成的批量查询如下图所示:

    注意其他的查询API也支持批量查询。

    预先加载和延迟加载比较

    假设我们有下面的简单模型,如下图所示:

    image

    如果已经在数据库中存储了三个person实体,我们执行下面的代码:

    var listOfPersons = session.Query<Person>();
    foreach (var person in listOfPersons)
    {
        Console.WriteLine("{0} {1}", person.LastName, person.FirstName);
        foreach (var hobby in person.Hobbies)
        {
            Console.WriteLine(" {0}", hobby.Name);
        }
    }

    在NHibernate profiler中的结果如下图所示:

    这是典型的select(n+1)问题。NHibernate首先加载所有person列表,然后当访问每个人的爱好时,延迟加载各自爱好的列表。这在大多数情况下都是需要的,尤其是只想访问person实体的属性时或者是访问一部分人的爱好时。

    另一方面,如果需要访问所有person的爱好,我们有一种更好的方式加载数据。我们可以告诉NHibernate和person实体一起预先加载所有的爱好。

    LINQ to NHibernate中的Fetch方法可以达到这个目的。下面的查询,NHibernate只发送一个语句到数据库:

    var persons = session.Query<Person>().Fetch(p => p.Hobbies);

    NHibernate生成的SQL如下图所示:

    NHibernate使用左外连接一次加载person和爱好。

    也可以使用条件查询获得同样的结果,如下面的代码所示:

    var persons = session.CreateCriteria<Person>("p")
                .CreateCriteria("p.Hobbies", JoinType.LeftOuterJoin)
                .List<Person>();

    这里,我们使用CreateCriteria方法定义如何处理独立爱好集合,我们声明了使用左外连接。查询的结果跟使用LINQ提供程序是一样的。

    注意我们使用左外连接而不是内连接,因为person可以没有爱好。

    最后还可以使用HQL在person和爱好之间使用左外连接操作获得同样的结果,代码如下所示:

    var hql = "select p from Person as p left join fetch p.Hobbies as h";
    var listOfPersons = session.CreateQuery(hql)
    .List<Person>();

    由NHibernate生成的数据库查询跟LINQ提供程序生成的一样。

    批量数据修改

    在前面的文章中,我们学习了在NHibernate的帮助下,添加新纪录到数据库,修改、删除存在的记录。这些操作都是基于单个记录的。这是预期的行为,大多数情况下都能满足我们的需求。然而,有时我们想一次对整个数据集合进行修改。NHibernate允许我们使用HQL执行大容量数据的修改。为此,我们可以使用ExecuteUpdate方法,它定义在IQuery接口中。

    使用一个查询更新所有产品的单价,代码如下所示:

    var hql = "update Product p set p.UnitPrice = 1.1 * p.UnitPrice";
    session.CreateQuery(hql).ExecuteUpdate();

    NHibernate发送下面的命令到数据库,如下图所示:

    批量删除断货的产品,代码如下所示:

    var hql = "delete Product p where p.Discontinued=true";
    session.CreateQuery(hql).ExecuteUpdate();

    最后使用HQL批量插入,代码如下:

    var hql = "insert into Product(Id, Name, Category, UnitPrice) " +
    "select t.Id, t.Name, t.Category, t.UnitPrice " +
    "from ProductTemp t";
    session.CreateQuery(hql).ExecuteUpdate();

    批量插入的一些注意事项:

    • 数据源必须是数据库的一个(映射表)表。
    • 数据源和目标表的所有列必须匹配。
    • 不能使用值是由数据库自动生成的Id列。
  • 相关阅读:
    数据系统与分布式(二) 分布式数据系统(复制与分片)
    数据系统和分布式(一)数据系统基础
    可执行文件(ELF)的装载与进程
    HTTPS协议
    后台开发 缓存, 数据库, 高并发等等
    Golang中new和make的区别
    吴恩达:机器学习里面的作业1遇到的
    笔记——操作系统导论:环境配置
    笔记——操作系统导论:第二章
    Games 101 作业1代码解析
  • 原文地址:https://www.cnblogs.com/nianming/p/2265906.html
Copyright © 2011-2022 走看看