zoukankan      html  css  js  c++  java
  • NHibernate学习笔记(2)—关系映射

    NHibernate中的集合类型

    NHibernate支持/定义的几种类型的集合:

    Bag:对象集合,每个元素可以重复。例如{1,2,2,6,0,0},在.Net中相当于IList或者IList<T>实现。

    Set:对象集合,每个元素必须唯一。例如{1,2,5,6},在.Net中相当于ISet或者ISet<T>实现,Iesi.Collections.dll程序集提供ISet集合。

    List:整数索引对象集合,每个元素可以重复。例如{{1,"YJingLee"},{2,"CnBlogs"},{3,"LiYongJing"}},在.Net中相当于ArraryList或者List<T>实现。

    Map:键值对集合。例如{{"YJingLee",5},{"CnBlogs",7},{"LiYongJing",6}},在.Net中相当于HashTable或者IDictionary<Tkey,TValue>实现。

    实际上,我们大多数情况下使用Set集合类型,因为这个类型和关系型数据库模型比较接近。


    父子关联映射

    在NHibernate中,我们可以通过映射文件来关联对象之间的关系。映射文件定义了:

    • 对象之间关系:一对一、一对多、多对一、多对多关系。
    • 在关系中控制级联行为(Cascade behavior):级联更新、级联删除
    • 父子之间的双向导航(bidirectional navigation)

    1.父实体映射

    父实体(Customer)映射定义了:

    • 集合类型(Bag、Set、List、Map)
    • 在保存、更新、删除操作时的级联行为
    • 关联的控制方向:
      • Inverse="false"(默认):父实体负责维护关联关系
      • Inverse="true":子实体负责维护关联关系
    • 与子实体关联的关系(一对多、多对一、多对多)

    这些具体的设置是NHibernate中的难点所在,以后慢慢讨论这些不同设置下的奥秘之处。

    这一篇初步建立Customer与Order的一对多关系,修改Customer.hbm.xml映射文件如下:

    <?xml version="1.0" encoding="utf-8" ?>
    <
    hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    assembly
    ="DomainModel" namespace="DomainModel">

    <
    class name ="DomainModel.Entities.Customer,DomainModel"
    table="Customer">
    <
    id name="CustomerId" column="CustomerId" type="Int32"
    unsaved-value
    ="0">
    <
    generator class ="native"></generator>
    </
    id>
    <
    property name="Firstname" column ="Firstname" type="string"
    length="50" not-null="false"/>
    <
    property name ="Lastname" column="Lastname" type="string"
    length="50" not-null="false"/>
    <!--一对多关系:Customer有一个或多个Orders-->
    <
    set name="Orders" table="`Order`" generic="true" inverse="true">
    <
    key column="Customer" foreign-key="FK_CustomerOrders"/>
    <
    one-to-many class="DomainModel.Entities.Order,DomainModel"/>
    </
    set>

    </class>
    </
    hibernate-mapping>

    可以看到,在“父”端映射使用Set元素,标明属性名称、表名、子实体负责维护关联关系。

    2.子实体映射

    子实体(Order)映射定义的东西就是父实体少了:与父实体关联的(多对一、一对多、多对多) 关系,并用一个指针来导航到父实体。

    在“子”端通过many-to-one元素定义与“父”端的关联,从“子”端角度看这种关系模型是多对一关联(实际上是对Customer对象的引用)。下面看看many-to-one元素映射属性:

    many-to-one元素定义

    看看这些映射属性具体有什么意义:

    • access(默认property):可选field、property、nosetter、ClassName值。NHibernate访问属性的策略。
    • cascade(可选):指明哪些操作会从父对象级联到关联的对象。可选all、save-update、delete、none值。除none之外其它将使指定的操作延伸到关联的(子)对象。
    • class(默认通过反射得到属性类型):关联类的名字。
    • column(默认属性名):列名。
    • fetch(默认select):可选select和join值,select:用单独的查询抓取关联;join:总是用外连接抓取关联。
    • foreign-key:外键名称,使用SchemaExport工具生成的名称。
    • index:......
    • update,insert(默认true):指定对应的字段是否包含在用于UPDATE或INSERT 的SQL语句中。如果二者都是false,则这是一个纯粹的 “外源性(derived)”关联,它的值是通过映射到同一个(或多个)字段的某些其他特性得到或者通过触发器其他程序得到。
    • lazy:可选false和proxy值。是否延迟,不延迟还是使用代理延迟。
    • name:属性名称propertyName。
    • not-found:可选ignore和exception值。找不到忽略或者抛出异常。
    • not-null:可选true和false值。
    • outer-join:可选auto、true、false值。
    • property- ref(可选):指定关联类的一个属性名称,这个属性会和外键相对应。如果没有指定,会使用对方关联类的主键。这个属性通常在遗留的数据库系统使用,可能 有外键指向对方关联表的某个非主键字段(但是应该是一个唯一关键字)的情况下,是非常不好的关系模型。比如说,假设Customer类有唯一的 CustomerId,它并不是主键。这一点在NHibernate源码中有了充分的体验。
    • unique:可选true和false值。控制NHibernate通过SchemaExport工具生成DDL的过程。
    • unique-key(可选):使用DDL为外键字段生成一个唯一约束。

    我们来建立“子”端到“父”端的映射,新建Order.hbm.xml文件,编写代码如下:

    <?xml version="1.0" encoding="utf-8" ?>
    <
    hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    assembly="DomainModel" namespace="DomainModel">

    <
    class name="DomainModel.Entities.Order,DomainModel" table="`Order`" >
    <
    id name="OrderId" column="OrderId" type="Int32" unsaved-value="0">
    <
    generator class="native" />
    </
    id>
    <
    property name="OrderDate" column="OrderDate" type="DateTime"
    not-null
    ="true" />
    <!--
    多对一关系:Orders属于一个Customer-->
    <
    many-to-one name="Customer" column="Customer" not-null="true"
    class="DomainModel.Entities.Customer,DomainModel"
    foreign-key="FK_CustomerOrders"
    />
    </
    class>
    </
    hibernate-mapping>

    关于如何关联看看上面的属性就一目了然了。


    预过滤

    使用ICriteria接口的SetResultTransformer(IResultTransformer resultTransformer)方法返回满足特定条件的Customer。上面例子中使用条件查询,观察其生成的SQL语句并没有 distinct,这时可以使用NHibernate.Transform命名空间中的方法或者使用NHibernate提供的 NHibernate.CriteriaUtil.RootEntity、 NHibernate.CriteriaUtil.DistinctRootEntity、 NHibernate.CriteriaUtil.AliasToEntityMap静态方法实现预过滤的作用。那么上面的查询应该修改为:

    public IList<Customer> UseCriteriaAPI_GetCustomersWithOrders(DateTime orderDate)
    {
    return _session.CreateCriteria(typeof(Customer))
    .CreateCriteria("Orders")
    .Add(Restrictions.Gt("OrderDate", orderDate))
    .SetResultTransformer(new NHibernate.Transform.DistinctRootEntityResultTransformer())
    //或者.SetResultTransformer(NHibernate.CriteriaUtil.DistinctRootEntity)
    .List<Customer>();
    }

    这个例子从转换结果集的角度实现了我们想要的效果。

    不太理解的地方

    投影

    调用SetProjection()方法可以实现应用投影到一个查询中。NHibernate.Criterion.Projections是Projection的实例工厂,Projections提供了非常多的方法,看看下面的截图,下拉列表中的方法是不是很多啊:

    Projections方法

    现在可以条件查询提供的投影来完成上面同样的目的:

    public IList<Customer> UseCriteriaAPI_GetDistinctCustomers(DateTime orderDate)
    {
    IList<int> ids = _session.CreateCriteria(typeof(Customer))
    .SetProjection(Projections.Distinct(Projections.ProjectionList()
    .Add(Projections.Property("CustomerId"))
    )
    )
    .CreateCriteria("Orders")
    .Add(Restrictions.Gt("OrderDate", orderDate))
    .List<int>();

    return _session.CreateCriteria(typeof(Customer))
    .Add(Restrictions.In("CustomerId", ids.ToArray<int>()))
    .List<Customer>();
    }

    我们可以添加若干的投影到投影列表中,例如这个例子我添加一个CustomerId属性值到投影列表中,这个列表中的所有属性值都设置了 Distinct投影,第一句返回订单时间在orderDate之后所有顾客Distinct的CustomerId,第二句根据返回的 CustomerId查询顾客列表。达到上面的目的。这时发现其生成的SQL语句中有distinct。我们使用投影可以很容易的组合我们需要的各种方 法。


    对于不太理解的部分我框选了起来。

    1. 为什么要查询两次,明明一次SQL查询可以解决的问题却要查询两次。也许是种扩展是为了说明投影而说投影么?还是有什么别的原因?

    下面是我执行投影查询时检测到的SQL:

    NHibernate: SELECT distinct this_.CustomerId as y0_ FROM Customer this_ inner join [Order] order1_ on this_.CustomerId=order1_.Customer WHERE order1_.OrderDate > @p0;@p0 = 2008-10-1 0:00:00
    NHibernate: SELECT this_.CustomerId as CustomerId0_0_, this_.Version as Version0_0_, this_.Firstname as Firstname0_0_, this_.Lastname as Lastname0_0_ FROM Customer this_ WHERE this_.CustomerId in (@p0);@p0 = 1
    NHibernate: SELECT orders0_.Customer as Customer1_, orders0_.OrderId as OrderId1_, orders0_.OrderId as OrderId1_0_, orders0_.OrderDate as OrderDate1_0_, orders0_.Customer as Customer1_0_ FROM [Order] orders0_ WHERE orders0_.Customer=@p0;@p0 = 1

    其与代码执行的对应关系,据我分析应该如下:














    多对多映射关系

    1.Order有多个Products


    修改Order.cs类代码如下:

    namespace DomainModel.Entities
    {
    public class Order
    {
    public virtual int OrderId { get; set; }
    public virtual DateTime OrderDate { get; set; }
    //多对一关系:Orders属于一个Customer
    public virtual Customer Customer { get; set; }
    //多对多关系:Order有多个Products
    public virtual IList<Product> Products { get; set; }
    }
    }

    修改Order.hbm.xml映射文件如下:

    <?xml version="1.0" encoding="utf-8" ?>
    <
    hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    assembly="DomainModel" namespace="DomainModel">

    <
    class name="DomainModel.Entities.Order,DomainModel" table="`Order`" >

    <
    id name="OrderId" column="OrderId" type="Int32" unsaved-value="0">
    <
    generator class="native" />
    </
    id>
    <
    property name="OrderDate" column="OrderDate" type="DateTime" not-null="true" />
    <!--
    多对一关系:Orders属于一个Customer-->
    <
    many-to-one name="Customer" column="Customer" not-null="true"
    class="DomainModel.Entities.Customer,DomainModel"
    foreign-key="FK_CustomerOrders" />
    <!--
    多对多关系:Order有多个Products-->
    <
    bag name="Products" generic="true" table="OrderProduct">
    <
    key column="`Order`" foreign-key="FK_OrderProducts"/>
    <
    many-to-many column="Product"
    class ="DomainModel.Entities.Product,DomainModel"
    foreign-key="FK_ProductOrders"/>
    </
    bag
    >
    </
    class>
    </
    hibernate-mapping>

    在多对多关系中,其两方都使用Bag集合和many-to-many元素。看看上面各个属性和one-to-many,many-to-one属性差不多。

    2.Product属于多个Orders

    在项目DomainModel层的Entities文件夹中新建Product.cs类,编写代码如下:

    namespace DomainModel.Entities
    {
    public class Product
    {
    public virtual int ProductId { get; set; }
    public virtual string Name { get; set; }
    public virtual float Cost { get; set; }
    //多对多关系:Product属于多个Orders
    public virtual IList<Order> Orders { get; set; }
    }
    }

    在项目DomainModel层的Mappings文件夹中新建Product.hbm.xml映射文件,编写代码如下:

    <?xml version="1.0" encoding="utf-8" ?>
    <
    hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    assembly="DomainModel" namespace="DomainModel">
    <
    class name="DomainModel.Entities.Product,DomainModel" table="Product">

    <
    id name="ProductId" column ="ProductId" type="Int32" unsaved-value="0">
    <
    generator class="native"/>
    </
    id>
    <
    property name="Name" column="Name" type="string" not-null="true" length="50"/>
    <
    property name="Cost" column="Cost" type="float" not-null="true"/>
    <!--
    多对多关系:Product属于多个Orders-->
    <
    bag name="Orders" generic="true" table="OrderProduct">
    <
    key column="Product" foreign-key="FK_ProductOrders"/>
    <
    many-to-many column="`Order`"
    class="DomainModel.Entities.Order,DomainModel"
    foreign-key="FK_OrderProducts"/>
    </
    bag
    >
    </
    class>
    </
    hibernate-mapping>

    使用NHibernate中提供的三种查询方法实现多对多关联查询,查询返回所有订单和产品的顾客列表。

    1.原生SQL关联查询

    public IList<Customer> UseSQL_GetCustomersWithOrdersHavingProduct(DateTime orderDate)
    {
    return _session.CreateSQLQuery("select distinct {customer.*} from Customer {customer}" +
    " inner join [Order] o on o.Customer={customer}.CustomerId"+
    " inner join OrderProduct op on o.OrderId=op.[Order]"+
    " inner join Product p on op.Product=p.ProductId where o.OrderDate> :orderDate")
    .AddEntity("customer", typeof(Customer))
    .SetDateTime("orderDate", orderDate)
    .List<Customer>();
    }

    这里需要使用Join告诉查询如何在表之间关联。无论多么复杂的关系,我们必须在查询语句中指定返回值。这里使用AddEntity设置返回的实体。

    2.HQL关联查询

    public IList<Customer> UseHQL_GetCustomersWithOrdersHavingProduct(DateTime orderDate)
    {
    return _session.CreateQuery("select distinct c from Customer c ,"
    + " c.Orders.elements o where o.OrderDate > :orderDate")
    .SetDateTime("orderDate", orderDate)
    .List<Customer>();
    }

    因为在映射文件已经定义实体之间一对多、多对多关系,NHibernate通过映射文件知道如何去关联这些实体,我们不需要在查询语句中重复定义。这里使用查询和上一篇使用HQL关联查询语句一样,NHibernate完全可以去关联对象,实现查询订单和产品。

    3.Criteria API关联查询

    因为实体之间的关联我们在映射文件中已经定义好了。所以我们在查询子对象使用子CreateCriteria语句关联对象之间导航,可以很容易地在 实体之间指定约束。这里第二个CreateCriteria()返回ICriteria的新实例,并指向Orders实体的元素。第三个指向 Products实体的元素。

    public IList<Customer> UseCriteriaAPI_GetCustomerswithOrdersHavingProduct()
    {
    return _session.CreateCriteria(typeof(Customer))
    .Add(Restrictions.Eq("Firstname","YJing"))
    .CreateCriteria("Orders")
    .Add(Restrictions.Gt("OrderDate",new DateTime(2008,10,1)))
    .CreateCriteria("Products")
    .Add(Restrictions.Eq("Name","Cnblogs"))
    .List<Customer>();
    }
    对于多对多关联的查询对应的SQL语句如下:
    ------ Test started: Assembly: DALTest.dll ------

    NHibernate: SELECT this_.CustomerId as CustomerId0_2_, this_.Version as Version0_2_, this_.Firstname as Firstname0_2_, this_.Lastname as Lastname0_2_, order1_.OrderId as OrderId1_0_, order1_.OrderDate as OrderDate1_0_, order1_.Customer as Customer1_0_, products5_.[Order] as column1_, product2_.ProductId as Product, product2_.ProductId as ProductId3_1_, product2_.Name as Name3_1_, product2_.Cost as Cost3_1_ FROM Customer this_ inner join [Order] order1_ on this_.CustomerId=order1_.Customer inner join OrderProduct products5_ on order1_.OrderId=products5_.[Order] inner join Product product2_ on products5_.Product=product2_.ProductId WHERE this_.Firstname = @p0 and order1_.OrderDate > @p1 and product2_.Name = @p2;@p0 = 'Kaibo', @p1 = 2008-10-1 0:00:00, @p2 = 'Cnblogs'
    NHibernate: SELECT orders0_.Customer as Customer1_, orders0_.OrderId as OrderId1_, orders0_.OrderId as OrderId1_0_, orders0_.OrderDate as OrderDate1_0_, orders0_.Customer as Customer1_0_ FROM [Order] orders0_ WHERE orders0_.Customer=@p0;@p0 = 1
    NHibernate: SELECT products0_.[Order] as column1_1_, products0_.Product as Product1_, product1_.ProductId as ProductId3_0_, product1_.Name as Name3_0_, product1_.Cost as Cost3_0_ FROM OrderProduct products0_ left outer join Product product1_ on products0_.Product=product1_.ProductId WHERE products0_.[Order]=@p0;@p0 = 1
    NHibernate: SELECT products0_.[Order] as column1_1_, products0_.Product as Product1_, product1_.ProductId as ProductId3_0_, product1_.Name as Name3_0_, product1_.Cost as Cost3_0_ FROM OrderProduct products0_ left outer join Product product1_ on products0_.Product=product1_.ProductId WHERE products0_.[Order]=@p0;@p0 = 2

    另外还有一个需要注意的地方:对于数据库的关键字如:“Order” 在使用为字段名时,在配置文件中要对该“Column”配置加上特殊转义标记,以示区别,否则会报sql语法错误。
    如:
    对于SQL数据库关键字的转义<!--多对多关系:Product属于多个Orders-->
        <bag name="Orders" generic="true" table="OrderProduct">
          <key column="Product" foreign-key="FK_ProductOrders"/>
          <many-to-many column="[Order]"
                      class="DomainModel.Entities.Order,DomainModel"
                      foreign-key="FK_OrderProducts"/>
        </bag>
    以下是本篇的相关代码:NHibernateSample2.zip
    参考资料:

    NHibernate之旅(9):探索父子关系(一对多关系)

    NHibernate之旅(10):探索父子(一对多)关联查询

    NHibernate之旅(11):探索多对多关系及其关联查询

    本文部分内容摘录自:

    YJingLee's Blog

  • 相关阅读:
    linux基础命令篇四
    linux基础命令篇三
    linux基础命令篇二
    msf测试
    msf
    [精品转载] [NoSaFe]KALI下免杀神器TheFatRat使用秘籍
    kali&BT安装好之后无法上网或者无法获得内网IP
    Kali Linux安装之后需要做的一些事
    在Ubuntu下解决E: 无法对目录 /var/lib/apt/lists/ 加锁的问题(转)
    xshell之类的软件第一次连接不上初次安装kali问题(转)
  • 原文地址:https://www.cnblogs.com/haokaibo/p/NHibernate.html
Copyright © 2011-2022 走看看