Fluent NHibernate之旅(四)-- 关系

今天我们将说一下ORM中的R映射,我们现在的数据库大多都是关系型数据库了,所以可以说关系在我们数据库设计中也是非常重要的部分,NHibernate也非常重视这一块,但在传统方式中,配置就比较麻烦,不是说我们Fluent能简单,只是传统方式的xml看上去不太美观,而Fluent这种代码式方式,更能符合我们Developer的习惯。

  数据库关系

  数据库关系一般有:

  1、一对一

  2、一对多

  3、多对多

  开始

  结合我们前三个系列的示例,我们这一次加一个用户表[User],目的就是存储用户信息所用,再加一个UserDetail,作为用户的详细信息。怎么简单怎么来,数据库设计如下:


够简单的吧,User和UserDetail是一对一关系,构建我们的实体类:


public class User 
{ 
  public virtual int UserID { get; set; } 
 
  public virtual string UserName { get; set; } 
 
  public virtual string Password { get; set; } 
 
  public virtual DateTime CreateTime { get; set; } 
 
  public virtual UserDetail Detail { get; set; } 
} 
 
public class UserDetail 
{ 
  public virtual User User { get; set; } 
 
  public virtual int UserID { get; set; } 
 
  public virtual DateTime LastUpdated { get; set; } 
 
  public virtual PersonName Name { get; set; } 
} 
 
public class PersonName 
{ 
  public virtual string FirstName { get; set; } 
 
  public virtual string LastName { get; set; } 
}

嘿,为什么是三个model呢,因为我发现前几个系列里,没有说一下Component Mapping,所以今天一并说了。这是我们简单的一个一对一的设计,我们先只要求实现映射,至于其他的比如延迟加载的,稍后说。先跑起来溜溜。

  映射

  Fluent NHibernate 映射代码:


public class UserMap : ClassMap<User> 
{ 
  public UserMap() 
  { 
    Id(u => u.UserID).GeneratedBy.Identity() ; 
    Map(u => u.UserName); 
    Map(u => u.Password); 
    Map(u => u.CreateTime); 
    HasOne<UserDetail>(u => u.Detail).Cascade.All().PropertyRef("User"); 
  } 
} 
 
public class UserDetailMap : ClassMap<UserDetail> 
{ 
  public UserDetailMap() 
  { 
    Id(u => u.UserID).Column("UserID").GeneratedBy.Foreign("User"); 
    HasOne<User>(d => d.User).Cascade.All().Constrained(); 
    Map(u => u.LastUpdated).Nullable(); 
    Component<PersonName>(u => u.Name, p => 
    { 
      p.Map(o => o.FirstName).Column("[First Name]"); 
      p.Map(o => o.LastName).Column("[Last Name]"); 
    }); 
  } 
}

 代码中有几点要注意(红色标记):因为UserDetail使用的主键ID与User的ID是一致的,所以我们要使用Foregin来获取User的ID。Foreign的用法与先前版本有一点不同,需要指定propertyName。很多关联方法都是与NHibernate很类似的,比如Cascade,Cascade.All代表的是cascade="all",代表的是无论什么操作,都会同时操作关联对象。

  映射完,我们测试一下:


public void CreateUserTest() 
{ 
  var factory = FluentSessionFactory.GetCurrentFactory(); 
  using (var session = factory.OpenSession()) 
  { 
    DateTime createTime = DateTime.ParseExact("2009-07-08 11:00", "yyyy-MM-dd hh:ss",null); 
    User user = new User() 
    { 
      CreateTime = createTime, 
      Password = "ilovecandy", 
      UserName = "james", 
    }; 
 
    UserDetail detail = new UserDetail 
    { 
      Name = new PersonName { FirstName = "James", LastName = "Ying" }, 
      LastUpdated = createTime, 
    }; 
 
    detail.User = user; 
    user.Detail = detail; 
 
    session.Save(user); 
    session.Flush(); 
  } 
} 
 
[Fact] 
public void SelectUserTest() 
{ 
   var factory = FluentSessionFactory.GetCurrentFactory(); 
   using (var session = factory.OpenSession()) 
   { 
     User user = session.Get<User>(1); 
     Assert.Equal("James", user.Detail.Name.FirstName); 
   } 
}

从这篇以后,单元测试会使用Xunit,可以点此下载

  一个插入测试,一个查询测试,看看测试结果:


output:

  ok,测试通过。我们的一对一简单映射也说完了,同时也完成了Component的映射,接下来说说延迟加载

  一对一延迟加载

  细心的朋友一定会发现我们的output出来的Sql语句,使用的是联合查询,但有时对我们来说,只需要User就可以了,我不需要查询UserDetail,或许你会说,使用以下方式来进行延迟加载:

HasOne<UserDetail>(u => u.Detail).Cascade.All().LazyLoad();

虽然Fluent支持,虽然编译通过,但在创建ISessionFactory的时候,却会抛出异常,因为NHibernate不支持one-to-one的Lazy的特性,也就是说NHibernate不支持一对一的延迟加载。但是查了很多资料,说可以用:
HasOne<UserDetail>(u => u.Detail).Cascade.All().Fetch.Select();
 HasOne<User>(d => d.User).Cascade.All().Constrained();

进行延迟加载,但结果只是分了2条Sql语句进行的查询,并不是延迟加载,这一点可以通过Sql Server Profiler查看:

NHibernate是不支持one-to-one的延迟加载的,我也不知道为什么,但我们可以婉转的进行延迟加载,老赵已经在他的文章“ NHibernate中一对一关联的延迟加载”中提出了解决方案,大家可以看一下

场景和数据库设计

  延续我们的演示范例,用户和订单是非常典型的一对多范例。

  1、一个用户可以拥有多个订单

  2、一个订单只能拥有一个用户

  对于用户来说,不需要每次都加载订单列表,反之订单可能每次都需要加载用户信息。Let's Go:


我们原先的订单系统太贫血了,我们进一步扩展一下,现在已经可以储存收货人的姓名和地址,还包括了发起人的UserID。

  映射

  不得不赞叹一下 Fluent Nhibernate ,有了它,我们的映射一切都变得如此简单,先来看看Model吧,用户的订单列表,对于用户来说,暂时是不需要排序的,所以我们可以使用ISet作为Order的列表。


public class User 
{ 
  public virtual int UserID { get; set; } 
 
  public virtual string UserName { get; set; } 
 
  public virtual string Password { get; set; } 
 
  public virtual DateTime CreateTime { get; set; } 
 
  public virtual UserDetail Detail { get; set; } 
 
  public ISet<Order> Orders { get; set; } 
} 
 
public class Order 
{ 
  public virtual int OrderID { get; set; } 
 
  public virtual float Price { get; set; } 
 
  public virtual OrderState State { get; set; } 
 
  public virtual DateTime CreateTime { get; set; } 
 
  public virtual User User { get; set; } 
 
  public virtual string Address { get; set; } 
 
  public virtual string Zip { get; set; } 
 
  public virtual string Coignee { get; set; } 
}

好,我们看看Fluent如何映射吧,你会发觉,一切就是这么简单:


public class UserMap : ClassMap<User> 
{ 
  public UserMap() 
  { 
    Id(u => u.UserID).GeneratedBy.Identity() ; 
    Map(u => u.UserName); 
    Map(u => u.Password); 
    Map(u => u.CreateTime); 
    HasOne<UserDetail>(u => u.Detail).Cascade.All().Fetch.Select(); 
    HasMany<Order>(u => u.Orders).AsSet().KeyColumn("UserID").Cascade.All(); 
  } 
} 
 
public class OrderMap : ClassMap<Order> 
{ 
  public OrderMap() 
  { 
    Id(o => o.OrderID).GeneratedBy.Identity(); 
    Map(o => o.Price); 
    Map(o => o.State).CustomType<OrderState>(); 
    Map(o => o.Address); 
    Map(o => o.Coignee); 
    Map(o => o.CreateTime); 
    Map(o => o.Zip); 
    References<User>(o => o.User).Not.LazyLoad().Column("UserID"); 
  } 
}

怎么样,简单明了吧,比传统方式要容易懂吧,看一下我们的测试结果:

[Fact] 
public void CreateOrder() 
{ 
  var factory = FluentSessionFactory.GetCurrentFactory(); 
  using (var session = factory.OpenSession()) 
  { 
    User user = session.Get<User>(1); 
    Order order = new Order() 
    { 
      Address = "James & Candy 's Home", 
      Coignee = "Candy", 
      CreateTime = DateTime.Now, 
      Price = 1500, 
      State = OrderState.Created, 
      Zip = "200000", 
    }; 
 
    order.User = user; 
    session.Save(order); 
  } 
}


一对多的映射,比起一对一来说还相对的简单点,默认是延迟加载,如果项目中,有些地方,需要立即加载,我们也可以使用 FetchMode.Eager 来加载。

  立即加载 



我们在Output中,能看到NHibernate生成的Sql语句,测试也成功,说明我们刚刚是立即加载了Orders属性。

场景和数据库设计

  前两篇我们介绍了“一对一”和“一对多(多对一)”,或许前两种用的比较多,但多对多的关系,有时候我们也会遇到,比如我们一直演示的电子商务站,我们的订单和产品的关系,就是一个非常典型的“多对多”。看看我们的数据库设计:


这里说一下,订单对于产品来说,不一定需要知道,也或者可以不需要一起加载,所以可以用延迟加载或者不加载,而产品对于订单来说,应该是需要立即加载,从而知道订单中所有的商品。随着扩展,我们必须给我们的Product和Order加入相关的属性:

public abstract class Product 
{ 
  public virtual int ProductID { get; set; } 
 
  private ISet<Order> m_orders = null; 
  public ISet<Order> Orders 
  { 
    get 
    { 
      if (this.m_orders == null) 
      { 
        this.m_orders = new HashedSet<Order>(); 
      } 
      return this.m_orders; 
    } 
    set 
    { 
      this.m_orders = value; 
    } 
  } 
 
  //product other Property 
} 
 
public class Order 
{ 
  public Order() 
  { 
    this.Products = new HashedSet<Product>(); 
  } 
 
  public virtual int OrderID { get; set; } 
 
  public ISet<Product> Products { get; set; } 
 
  //order other Property 
}

 映射

  如果大家先前几篇都看过的话,我觉得应该没有任何问题了,因为Fluent NHibernate 真的很简单,很流畅,代码如下:


public class ProductMap : ClassMap<Product> 
{ 
  public ProductMap() 
  { 
    Id(p => p.ProductID); 
    HasManyToMany<Order>(p => p.Orders) 
      .AsSet() 
      .LazyLoad() 
      .ParentKeyColumn("ProductID") 
      .ChildKeyColumn("OrderID") 
      .Table("OrderProduct"); 
 
    Map(p => p.CreateTime); 
    Map(p => p.Name); 
    Map(p => p.Price); 
  } 
} 
 
public class OrderMap : ClassMap<Order> 
{ 
  public OrderMap() 
  { 
    Id(o => o.OrderID).GeneratedBy.Identity(); 
    HasManyToMany<Product>(o => o.Products) 
      .AsSet() 
      .Not.LazyLoad() 
      .Cascade.All() 
      .ParentKeyColumn("OrderID") 
      .ChildKeyColumn("ProductID") 
      .Table("OrderProduct"); 
 
    Map(o => o.Price); 
    Map(o => o.State).CustomType<OrderState>(); 
    Map(o => o.Address); 
    Map(o => o.Coignee); 
    Map(o => o.CreateTime); 
    Map(o => o.Zip); 
    References<User>(o => o.User).Not.LazyLoad().Column("UserID"); 
  } 
}

 这里我们用了一个单独的一个表来保存这个多对多关系,所以需要Table("Table Name")。

  ParentKeyColumn和ChildKeyColumn都是相对于自己的,大家也可以生成hbm来看下多对多的传统的写法。

  测试

  映射完成了,我们测试一下,我们还是使用xunit来单元测:


[Fact] 
public void CreateOrder() 
{ 
  using(var session = this.SessionFactory.OpenSession()) 
  { 
    session.Transaction.Begin(); 
    var products = session.CreateCriteria<Product>().List<Product>(); 
    var user = session.Load<User>(1); 
    var order = new Order 
    { 
      User = user, 
      Address = "Shang Hai", 
      Coignee = "Candy", 
      State = OrderState.Created, 
      CreateTime = DateTime.Now, 
      Zip = "200336" 
    }; 
 
    order.Products.AddAll(products); 
    order.Price = order.Products.Sum(p => p.Price); 
 
    session.Save(order); 
    session.Transaction.Commit(); 
  } 
}

(测试代码不是很完全,在文章结尾会有源代码,大家可以下载进行学习。)

  ok,我们来看下我们的测试结果,我们需要:绿



代码 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值