Save entities to the database

Clearly we have saved entities to the database in many of the previous recipes, but it's time to dig a bit deeper into how that actually works. This recipe will explore different ways of saving and what happens behind the scenes.

Getting ready

  1. Create a new Windows forms project named SessionRecipes, in Visual Studio.
  2. Add a reference to NHibernate using NuGet Packet Manager Console:
    Install-Package NHibernate -project SessionRecipes
    
  3. Also add a reference to NH4CookbookHelpers:
    Install-Package NH4CookbookHelpers -project SessionRecipes
    
  4. Remove the class Form1.cs from the project.
  5. Add using NH4CookbookHelpers; to the top of Program.cs.
  6. Edit Program.cs so that the last line in Main reads:
    Application.Run(new WindowsFormsRunner());

Using NHCookbookHelpers you don't even have to have a database server available, since it uses an in-memory SQLite database by default. If you want to use something else, like a local SQL server, you can specify a configuration instance to be used. Perhaps the most convenient way is to use a coded configuration, as described in Chapter 1, The Configuration and Schema.

Specify the default configuration before the last line in Main:

RecipeLoader.DefaultConfiguration = new Configuration()
.DataBaseIntegration(db =>
{
    db.Dialect<MsSql2012Dialect>();
    db.Driver<Sql2008ClientDriver>();
    db.ConnectionString = 
      @"Server=.SQLEXPRESS;Database=NHCookbook;
Trusted_Connection=True;";
});

Note that the database used in the recipes will be tinkered with, so don't use a database with data that you need to stay intact.

How to do it…

  1. Add a new folder named SavingEntities to the SessionRecipes project.
  2. Add a new class named Recipe to the folder:
    using System;
    using NH4CookbookHelpers.Queries;
    using NH4CookbookHelpers.Queries.Model;
    using NHibernate;
    
    namespace SessionRecipes.SavingEntities
    {
      public class Recipe : QueryRecipe
      {
        protected override void Run(ISessionFactory 
          sessionFactory)
        {
          PerformSave(sessionFactory);
          PerformUpdate(sessionFactory);
    
          TestFlushMode(sessionFactory, FlushMode.Auto);
          TestFlushMode(sessionFactory, FlushMode.Commit);
          TestFlushMode(sessionFactory, FlushMode.Never);
        }
    
        private void PerformSave(ISessionFactory 
          sessionFactory)
        {
          Console.WriteLine("PerformSave");
    
          var product = new Product { Name = "PerformSave" };
    
          using (var session = sessionFactory.OpenSession())
          {
            using (var tx = session.BeginTransaction())
            {
    
              session.Save(product);
              Console.WriteLine("Id:{0}", product.Id);
              tx.Commit();
            }
          }
        }
    
        private void PerformUpdate(ISessionFactory 
          sessionFactory)
        {
          Console.WriteLine("PerformUpdate");
          Product product;
    
          using (var firstSession = sessionFactory
            .OpenSession())
          {
            using (var tx = firstSession.BeginTransaction())
            {
              product = firstSession.Get<Product>(1);
              tx.Commit();
            }
          }
    
          product.Description += "-Updated by PerformUpdate";
    
          using (var secondSession = sessionFactory
            .OpenSession())
          {
            using (var tx = secondSession.BeginTransaction())
            {
              secondSession.Update(product);
              tx.Commit();
            }
          }
        }
    
        private void TestFlushMode(ISessionFactory 
          sessionFactory, FlushMode flushMode)
        {
          var name = "TestPublisher" + flushMode;
    
          using (var session = sessionFactory.OpenSession())
          {
            session.FlushMode = flushMode;
    
            using (var tx = session.BeginTransaction())
            {
              var publisher = new Publisher { Name = name};
              Console.WriteLine("Saving {0}", name);
              session.Save(publisher);
    
              Console.WriteLine("Searching for {0}", name);
    
              var searchPublisher = session
                .QueryOver<Publisher>()
                .Where(x => x.Name == name)
                .SingleOrDefault();
    
              Console.WriteLine(searchPublisher != null ?
    "Found it!" : "Didn't find it!");
    
              tx.Commit();
            }
    
            using (var tx = session.BeginTransaction())
            {
              Console.WriteLine(
    "Searching for {0} again", name);
    
               var searchPublisher = session
                 .QueryOver<Publisher>()
                 .Where(x => x.Name == name)
                 .SingleOrDefault();
    
               Console.WriteLine(searchPublisher != null ?
    "Found it!" : "Didn't find it!");
            }
          }
        }
      }
    }
  3. Run the application and start the SavingEntities recipe. You should be able to see the following output:
    How to do it…

How it works…

In the preceding recipe, we use a couple of different session methods to insert and update entities in the database.

Save

This is probably the method you will use most frequently. It's used when you have a fresh new entity that you want to save to the database. To summarize what it does:

  • Attach the entity to the session
  • Assign the Id to the entity, even if that requires a database roundtrip which possibly saves the entire entity
  • Follow any cascading rules for related entities
  • Return the assigned Id
  • When the session is flushed, if still necessary, insert the entity into the database

Calling Save more than once with the same entity and the same session works just fine, but is basically pointless. However, saving the same entity in more than one session will actually cause multiple database inserts.

Update

We're updating entities all the time, so isn't this the most frequently used method? No, probably not. As we will discuss later in this recipe, updates are usually handled automatically when the session is flushed. So, what's the purpose of Update then? It can be used if you have an entity that was loaded in one session but the update is delayed and performed later in another session. In short:

  • Attach the entity to the session and mark is as existing
  • Follow any cascading rules for related entities
  • When the session is flushed, update the entity in the database

Calling Update more than once for the same entity will cause an exception to be thrown.

SaveOrUpdate

You probably guessed it. SaveOrUpdate is a combination of the previous methods. If the session determines that the entity was already saved, it will trigger an Update, otherwise a Save.

Persist

This method is very similar to Save, but with a few differences. Most notably, it's a void method that does not return the assigned Id as Save does. Session.Persist doesn't guarantee that it will assign the Id to the entity. In theory, it can delay that action until the session is flushed. Another difference is that Persist will throw an exception if you pass a detached entity to it (an entity that was already saved but no longer connected to a session).

Using different flush modes

We have mentioned the term session flush a couple of times now. Maybe it's time we describe what it means.

NHibernate normally doesn't execute inserts, updates, and deletes as a response to explicit method calls. Instead it keeps the initial state of each loaded entity in memory and whenever a flush is triggered, this state is compared to the current state. Modified states will cause updates, new states will cause inserts and deleted states will cause deletes. This means that we don't have to ask NHibernate to update an entity. If it has been modified since it was loaded, the flushing process will pick this up and acts accordingly.

It may, however, be useful to control when this process occurs. Do we want it to happen automatically or should an explicit method call be necessary? For this purpose we can set the session's FlushMode property to one of several enum values.

Commit

When using this flush mode, changes are applied to the database when the current transaction is committed.

Auto

This is the default flush mode, but also the most advanced. Not only does it flush the session when a transaction is committed, it also attempts to deduce the entities or tables that are used in a query. If required, the session will be flushed before the query is executed, in order to make the query results reflect the state of the database, including all pending inserts, updates, and deletes.

Always

This flush mode acts similarly to Auto, but no query scanning logic is applied. The session will be flushed before every query. This mode is rarely used, but it does provide the best protection against consistency problems. While the Auto mode is very clever, there are occasions when NHibernate won't be able to extract the tables used, especially for native SQL queries. If you use such queries often and still want the consistency support offered by automatic flushing, the Always mode is a good choice.

Never

It does what it says on the box. Nothing is flushed to the database, unless explicitly triggered by calling session.Flush(). This is useful when we want to ensure that nothing is written to the database, unless we say so. An example can be a long running business process, perhaps spanning multiple sessions, which should be finalized only after several steps have been executed.

In the recipe, use the Publisher entity to show the behaviour of the different flush modes. If we instead use the Product entity, the searches will always return the entity. Why the difference? It's simply because Product has an integer Id, which is generated by the Native POID generator. In order to properly assign an Id to the new entity, the database must be contacted and when that is done, NHibernate might as well save the entity too. The Publisher class, on the other hand, has a Guid Id, which is assigned by NHibernate's comb generator. No roundtrip to the database is required and nothing will be saved until the session is flushed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.171.125