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.
SessionRecipes
, in Visual Studio.NHibernate
using NuGet Packet Manager Console:Install-Package NHibernate -project SessionRecipes
NH4CookbookHelpers
:Install-Package NH4CookbookHelpers -project SessionRecipes
Form1.cs
from the project.using NH4CookbookHelpers;
to the top of Program.cs
.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.
SavingEntities
to the SessionRecipes
project.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!"); } } } } }
SavingEntities
recipe. You should be able to see the following output:In the preceding recipe, we use a couple of different session methods to insert and update entities in the database.
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:
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.
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:
Calling Update
more than once for the same entity will cause an exception to be thrown.
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
.
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).
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.
When using this flush mode, changes are applied to the database when the current transaction is committed.
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.
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.
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.
18.189.171.125