zoukankan      html  css  js  c++  java
  • 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的增删查改有了一定的理解。

  • 相关阅读:
    MyBatis的Mapper接口以及Example的实例函数及详解
    数据存储
    广播
    java线程复习3(线程的中断)
    java线程复习2(获取和设置线程信息)
    java线程复习1(线程创建)
    最好的启动方式
    工厂模式
    欧几里得算法
    组合数打表
  • 原文地址:https://www.cnblogs.com/nianming/p/2251368.html
Copyright © 2011-2022 走看看