NHibernate初学者指南(8):增删查改

在开始之前有必要说一下会话(session)事务(transaction)

session和transaction是什么

session和transaction是NHibernate提供的最重要的两个对象。通过session对象,可以与数据库进行通信以及执行各种操作。transaction对象为我们提供了一个工具,允许以一个单元管理多个操作。

Session

Nhibernate session可以看成是通往数据库的抽象管道。现在,必须创建一个ADOConnection,打开Connection,传递Connection给Command对象,从Command对象创建DataReader的日子一去不复返了。使用NHibernate,只需向sessionFactory请求一个Session对象,就这么简单。通过session对象,可以往数据库里添加数据,更新以及删除数据库中已有的数据,也可以读取数据库中已有的数据。所有的这些操作都可以使用面向对象的方式执行,不必处理SQL字符串和特定数据库的复杂性。session对象允许我们与数据库中的数据通信,而不用管是SQL Server数据库、MySql数据库或者是Oracle数据库等等。NHibernate完全为我们抽象了这些细节。

Transaction

transaction允许我们以一个工作单元执行很多的任务,结果是要么所有的操作都执行成功,要么即使一个任务执行失败,所有的任务都会返回到原来状态。这种工作方式,我们也称之为“原子操作”。

transaction有四个特点:原子性、一致性、隔离性、持久性。我们也使用首字母ACID描述这些特点。事务操作必须满足ACID。

使用session工厂创建session

NHibernate使用一个工厂对象创建session实例。这个工厂对象称为“对话工厂(session factory)”。一个session工厂可以根据需要创建任意多的session对象。session对象的创建是非常廉价的操作。

另一方面,session工厂的创建非常昂贵。根据系统的复杂性,它可以花费相当长的时间创建一个session工厂实例。这就是为什么我们在一个程序整个生命周期内只创建一个session工厂实例的原因。

一个session工厂是特定于数据库的。如果我们的程序只需要与一个单独的数据库通信,那么只需要一个session工厂。另一方面,如果我们的程序需要几个不同的数据库,那么每个数据库都需要一个session工厂。

session工厂是线程安全的(thread-safe)。运行在不同线程上的代码可以使用同一个session工厂创建session对象。相比之下,session对象只能用在单个线程中。换句话说就是:session对象不是线程安全的。

创建NHibernate session非常简单,一旦有了sessionFactory对象,就可以调用OpenSession方法创建它:

var session = sessionFactory.OpenSession(); 

通常,不管操作的结果如何,我们都想在using语句中打开session以保证session被关闭和释放。我们还想启动一个事务来对操作的结果做更好的预测。

using (var session = sessionFactory.OpenSession()) 
{ 
using (var transaction = session.BeginTransaction()) 
{ 
// create, update, delete, or read data 
transaction.Commit(); 
} 
} 

添加数据

创建一个新的实体,可以调用session对象的Save方法持久化到数据库:

var newProductId = (int)session.Save(newProduct); 

注意Save方法返回新生成记录的ID。因为有不同的策略生成ID(int,long或GUID),所以返回类型为object类型,我们必须转换结果到预期的类型。我们还可以访问刚刚持久化的实体的ID属性获得新生成ID的值来代替使用Save方法的返回值并转换到正确的类型。

var newProductId = newProduct.Id; 

作为Save方法的替代方法,我们也可以使用session对象的Persist方法。Persist方法没有返回值,我们可以直接使用实体的ID属性获取新生成实体ID的值。这种情况下没有类型转换。

查询数据

根据指定的主键值从数据库中读取单条记录,可以使用session对象的Get或者Load方法。下面的代码从数据库中读取ID为1的Product实体:

var product = session.Get<Product>(1); 

使用Get或Load,我们必须知道要获取实体的ID。

如果想获取给定类型的实体列表,可以使用session对象的Query方法。这个方法是LINQ to NHibernate驱动程序的一部分。

获取所有的类别列表,可以使用下面的代码:

var allCategories = session.Query<Category>().ToList(); 

注意语句后边调用ToList()方法。LINQ to NHibernate返回IQuery<Category>类型的列表,,它是延迟加载,如果想NHibernate提前加载所有的记录,就要通过调用ToList()强制完成。

Get和Load

如果延迟加载启动的话,Get和Load有很大的区别。

如果想加载Product实体,可以使用下面的代码:

var product = session.Get<Product>(…?); 

Get方法需要加载的实体的主键值作为参数,所以加载ID为1的Product,可以使用下面的代码:

var product = session.Get<Product>(1); 

如果请求的实体在数据库中存在,Get方法会物理的从数据库中检索它。如果给定ID的实体在数据库中不存在,那么返回NULL。

如果使用Load方法,NHibernate不会从数据库检索实体,而是创建一个代理对象。这个代理实体唯一填充数据的属性是ID,如下面的代码所示:

var product = session.Load<Product>(1);

通过上面的代码,获得了一个ID为1的代理Product实体。此时,我们的代码会访问除ID以外的任何属性,NHibernate从数据库加载实体。实际上,这就是我们所谓的“延迟加载”。

那么什么情况下使用Load方法呢?需要访问和操作实体时,我们使用session对象的Get方法,那么不需要真的修改和访问实体的详细信息时使用Load方法。我们看个简单的例子。假设我们想更新一个存在Product实体改变它的类别。一件产品属于一个类别,因此,Product实体有一个Category类型的属性。然而,当操作products时,我们不想同时修改类别,只是使用它们。下面的代码能够实现:

var product = session.Get<Product>(productId); 
product.Category = session.Load<Category>(newCategoryId); 

NHibernate会生成一个SELECT语句加载一个product,但是不会加载category。它只是创建一个代理实体,分配一个新的类别给产品。

更新数据

因为NHibernate session对象跟踪对加载的对象任何修改,所以不用显示的调用update或其他方法持久化修改到数据库。此时session对象被刷新,修改会由NHibernate自动持久化。下面的代码可以达到目的:

using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction())
{
    var product = session.Get<Product>(1);
    product.UnitPrice = 10.55m;
    // new unit price
    tx.Commit();
}

删除数据

删除数据库中存在的实体,必须首先加载它,然后将它传给session对象的Delete方法,如下面的代码所示:

var productToDelete = session.Load<Product>(productId);
session.Delete(productToDelete);

注意为了避免不必要的数据库往返,可以使用Load方法来代替Get方法。当刷新session时,上面代码结果就是一个简单的删除SQL语句送到数据库。但是这并适用于要删除的实体依赖其他实体或子实体的集合。这种情况下,必须加载实体到内存中。

实战时间

在所有理论后,让我们实现一个例子。在这个例子中,我们定义一个简单的模型,并使用自动映射来映射这个模型。然后我们创建一个session工厂,用它来创建session对象。

1. 在SMSS中新建一个空的数据库:NHibernateSessionSample。

2. 打开VS,创建一个Console Application项目:NHibernateSessionSample。

3. 为项目添加NHibernate,NHibernate.ByteCode.Castle和FluentNHibernate程序的引用。

4. 在项目中添加一个Domain文件夹,在该文件夹中添加一个Order类。

public class Order
{
    public virtual int Id { get; set; }
    public virtual Customer Customer { get; set; }
    public virtual DateTime OrderDate { get; set; }
    public virtual IList<LineItem> LineItems { get; set; }
}

5. 在Order类中添加一个默认构造函数,初始化LineItems集合,如下面的代码所示:

public Order()
{
    LineItems = new List<LineItem>();
}

6. 在Order类中添加一个虚拟方法用来添加line item,如下面的代码所示:

public virtual void AddLineItem(int quantity, string productCode)
{
    var item = new LineItem
    {
        Order = this,
        Quantity = quantity,
        ProductCode = productCode
    };
    LineItems.Add(item);
}

7. 在Domain文件夹中分别添加LineItem类和Customer类,如下所示:

public class LineItem
{
    public virtual int Id { get; set; }
    public virtual Order Order { get; set; }
    public virtual int Quantity { get; set; }
    public virtual string ProductCode { get; set; }
}

 

public class Customer
{
    public virtual int Id { get; set; }
    public virtual string CustomerName { get; set; }
}

8. 在项目添加一个类文件:OrderingSystemConfiguration。添加下面的代码定义映射哪些类:

namespace NHibernateSessionSample
{
    public class OrderingSystemConfiguration : DefaultAutomappingConfiguration
    {
        public override bool ShouldMap(Type type)
        {
            return type.Namespace == typeof(Customer).Namespace;
        }
    }
}

9. 在Program类中为session工厂定义一个静态变量,如下所示:

private static ISessionFactory sessionFactory;

10. 在Program类中添加一个静态方法ConfigureSystem。这个方法配置NHibernate使用SQL Server作为数据库,以及使用自动映射来映射模型和自动创建数据库架构:

        private static void ConfigureSystem()
        {
            const string connString =
"server=.;database=NHibernateSessionSample;" +
"integrated security=SSPI;";
            var cfg = new OrderingSystemConfiguration();
            var model = AutoMap.AssemblyOf<Customer>(cfg);
            var configuration = Fluently.Configure()
                                .Database(MsSqlConfiguration.MsSql2008
                                .ConnectionString(connString)
                                .ShowSql
                                )
                                .Mappings(m => m.AutoMappings.Add(model))
                                .BuildConfiguration();
            var exporter = new SchemaExport(configuration);
            exporter.Execute(true, true, false);
            sessionFactory = configuration.BuildSessionFactory();
        }

注意上面代码中.ShowSql的调用。它配置NHibernate将发送到数据库的所有SQL语句输出到控制台

11. 在Program类中添加一个静态方法CreateCustomers。这个方法创建两个customer对象,并使用session对象将它们存储到数据库中,如下面的代码所示:

private static void CreateCustomers()
{
    var customerA = new Customer { CustomerName = "Microsoft" };
    var customerB = new Customer { CustomerName = "Apple Computer" };
    using (var session = sessionFactory.OpenSession())
    using (var transaction = session.BeginTransaction())
    {
        session.Save(customerA);
        session.Save(customerB);
        transaction.Commit();
    }
}

12. 在Main方法中,添加下面的代码:

static void Main(string[] args)
{
    ConfigureSystem();
    CreateCustomers();
    Console.Write("\r\nHit enter to exit:");
    Console.ReadLine();
}

13. 运行程序,结果如下图所示:

1

SQL命令的前几行显示了如果架构已经存在就将它们移除。通过执行上面的命令,在数据库中自动完成了数据库的架构,并在Customer表中插入了两条数据。

下面,我们创建一个订单。

14. 在Program类中添加一个静态方法CreateOrder,代码如下所示:

private static int CreateOrder()
{
    var customer = new Customer { CustomerName = "Intel" };
    var order = new Order
    {
        Customer = customer,
        OrderDate = DateTime.Now,
    };
    order.AddLineItem(1, "Apple");
    order.AddLineItem(5, "Pear");
    order.AddLineItem(3, "Banana");

    int orderId;
    using (var session = sessionFactory.OpenSession())
    using (var transaction = session.BeginTransaction())
    {
        session.Save(customer);
        orderId = (int)session.Save(order);
        transaction.Commit();
    }
    return orderId;
}

15. Main方法中,在CreateCustomers()之后,添加CreateOrder();如下面的代码所示:

static void Main(string[] args)
{
    ConfigureSystem();
    CreateCustomers();
    //添加在这里
    var orderId = CreateOrder();
    Console.Write("\r\nHit enter to exit:");
    Console.ReadLine();
}

16. 如果这时运行程序的话会出现异常,因为默认情况下,auto-mapper配置HasMany关系为none-inverse和不级联的。为了配置我们想要的映射,在ConfigureSystem方法中添加如下代码(在 var model = AutoMap.AssemblyOf<Customer>(cfg);之后):

model.Override<Order>(map => map.HasMany(x => x.LineItems).Inverse().Cascade.AllDeleteOrphan());

17. 这时再运行程序,一切OK,如下图所示:

QQ截图20111116153239

注意,3个order的line items自动地插入到了数据库中。

下面加载一个订单,删除它的一个line item,并添加一个新的:

18.  在Program类中添加一个UpdateOrder静态类,代码如下:

private static void UpdateOrder(int orderId)
{
    using (var session = sessionFactory.OpenSession())
    using (var transaction = session.BeginTransaction())
    {
        var order = session.Get<Order>(orderId);
        order.LineItems.RemoveAt(0);
        order.AddLineItem(2, "Apricot");
        transaction.Commit();
    }
}

19. 在Main方法中调用  UpdateOrder(orderId);

20. 运行程序,如下图所示:

QQ截图20111116154211

第一个select语句加载order。第二个select语句加载订单的line items。然后,一个insert语句,添加一个新的line item到数据库。最后,一个delete语句,从数据库中移除line item。

最后让我们删除订单。

21. 在Program类中添加一个DeleteOrder静态方法,代码如下:

private static void DeleteOrder(int orderId)
{
    using (var session = sessionFactory.OpenSession())
    using (var transaction = session.BeginTransaction())
    {
        var order = session.Load<Order>(orderId);
        session.Delete(order);
        transaction.Commit();
    }
}

22. 在Main方法中调用DeleteOrder(orderId);

23. 运行程序,控制台的输出如下:

QQ截图20111116155245

第一个select语句加载order,第二个加载order的line item。在订单本身删除之前,它的所有line item先删除。

总结

这篇文章首先介绍了什么是会话和事务,紧接着介绍了使用NHibernate进行增删查改的一些理论知识,最后通过一个控制台的例子,进行了实际的增删查改操作,并通过控制台的输出,可以看到生成的SQL语句。相信,通过这篇文章,你对NHibernate的增删查改有了一定的理解。

posted @ 2011-11-16 16:01  BobTian  阅读(6274)  评论(4编辑  收藏  举报