It seems that altough I have been using NHibernate for a while now, I still misunderstand some basic concepts of this ORM. Let's say I have a class called "Blog" and I load a persisted instance like so:
using (var tx = Session.BeginTransaction())
{
var myBlog = Session.Get(10);
tx.Commit();
}
If I now change a property of this instance, NHibernate seems to automatically detect the unsaved changes and will produce an UPDATE on transaction-commit.
This causes, that the following statements do exactly the same:
using (var tx = Session.BeginTransaction())
{
var myBlog = Session.Get(10);
myBlog.Title = "Changed title";
tx.Commit();
}
using (var tx = Session.BeginTransaction())
{
var myBlog = Session.Get(10);
myBlog.Title = "Changed title";
Session.Update(myBlog); // why is this necessary?
tx.Commit();
}
I don't see any difference with NHProf. So why does the explicit Update-method exists and when should I use it?
Entities are not always connected with session. For example you could have webservice with method, that accepts some entity, and updates in db:
[WebMethod]
void UpdatePerson(int id, string name){
using (var tx = Session.BeginTransaction(){
var person = new Person(id, name);
Session.Update(person);
tx.Commit();
}
}
This code executes update in database without issuing select.
Related
Why can't I just insert the model after I get an error back from the database when trying to insert it the first time:
Report report = null;
using (var session = SessionFactory.OpenSession()) {
try {
using (var transaction = session.BeginTransaction()) {
report = new Report();
session.SaveOrUpdate(report);//Exception: Name field required
transaction.Commit();
}
}
catch { }
try {
using (var transaction = session.BeginTransaction()) {
report.Name = "theName";
session.SaveOrUpdate(report);
//Causes Exception:
//Row was updated or deleted by another transaction (or unsaved-value
//mapping was incorrect): [ReportViewer.DataAccess.Models.Report#22]
transaction.Commit();
}
}
catch { }
}
But when I am updating an existing model, and I get an error, I can make my fixes (in this case set a Name) and just try to update again:
Report report = null;
using (var session = SessionFactory.OpenSession()) {
using (var transaction = session.BeginTransaction()) {
report = new Report();
report.Name = "theName";
session.SaveOrUpdate(report);
transaction.Commit();
}
}
using (var session = SessionFactory.OpenSession()) {
//get entity saved from previous session
report = session.Get<Report>(report.Id);
try {
using (var transaction = session.BeginTransaction()) {
report.Name = null;
session.SaveOrUpdate(report);//Exception: Name field required
transaction.Commit();
}
}
catch { }
try {
using (var transaction = session.BeginTransaction()) {
//updates and does not give an error
report.Name = "theName";
session.SaveOrUpdate(report);
transaction.Commit();
}
}
catch { }
}
When an exception triggered by the database occurs, the NHibernate session must be closed (disposed). It is not guaranteed to be consistent (internally or with the DB state) after an exception.
See the chapter on exception handling in the NHibernate reference.
As Oskar said, you should discard an NHibernate session after an exception occurs. However, the reason the insert fails is that you have already made the report persistent by calling SaveOrUpdate on it (you should use Save here). When you call SaveOrUpdate again on the same instance, NHibernate throws an exception because the object is already persistent. Rewriting the code as follows will probably allow the insert to succeed (but it's not recommended):
try {
using (var transaction = session.BeginTransaction()) {
report.Name = "theName";
transaction.Commit();
}
}
In the update example, calling SaveOrUpdate has no effect because the object became persistent when NHibernate loaded it. Understanding NHibernate's instance states and how to work with persistent objects is fundamental and widely misunderstood.
A far better approach is to validate your objects before saving them to the database.
I'm using an in-memory db for some quick unit tests, using the following code:
public class MemoryDb
{
private static Configuration configuration;
private static ISessionFactory sessionFactory;
static MemoryDb()
{
configuration = new NHibernate.Cfg.Configuration();
configuration.DataBaseIntegration(x =>
{
x.Driver<SQLite20Driver>();
x.Dialect<SQLiteDialect>();
x.ConnectionProvider<DriverConnectionProvider>();
x.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
x.IsolationLevel = IsolationLevel.ReadCommitted;
x.ConnectionString = "Data Source=:memory:;";
x.Timeout = 255;
x.BatchSize = 100;
x.LogFormattedSql = true;
x.LogSqlInConsole = true;
x.AutoCommentSql = false;
});
configuration.AddMapping(DbHelper.GetAutoMappings());
sessionFactory = configuration.BuildSessionFactory();
}
public static ISession GetSession()
{
var session = sessionFactory.OpenSession();
new SchemaExport(configuration).Execute(true, true, false, session.Connection, null);
return session;
}
}
The problem is that the schema export doesn't seem to be working. On of my tests looks like this:
[Fact]
public void ShouldFindDuplicateByEmail()
{
using (var session = MemoryDb.GetSession())
{
var repo = new NHibernateCustomerRepository(session);
var customer = new Customer();
customer.EmailAddress = "test#test.com";
repo.Save(customer);
var duplicates = repo.FindDuplicates(customer);
Assert.Equal(1, duplicates.Length);
}
}
The test fails with the error no such table: Customers. This all worked with Fluent NHibernate and NHibernate 3.1. I know it's not an issue with the mappings themselves, because the actual application works when I run it against an existing SQL Server db. It only fails when running the tests. Any thoughts?
Edit: If I change only the connection string such that it writes to a file (i.e. x.ConnectionString = "data source=" + Path.GetTempFileName();, the whole thing works. I'm guessing either the schema isn't run correctly against the in-memory db, or it's getting a new in-memory db each time I execute a session command, but have no clue how to figure this out.
I found the answer here: https://forum.hibernate.org/viewtopic.php?p=2397541#p2397541
I had to add the following to the db configuration:
x.ConnectionReleaseMode = ConnectionReleaseMode.OnClose;
Otherwise, NHibernate releases the connection after each statement is flushed, thereby getting rid of the in-memory database with the schema.
I was reading that Nhibernate exceptions lead to invalid session state. So, my question is which exceptions should I handle and close and reopen the session.? And, should I reload all entities?
My scenario - I am opening a session in my presenter class for a form. And, I am using transactions like
using (ITransaction transaction = session.BeginTransaction())
{
foreach (var item in records)
{
session.Delete(item);
}
transaction.Commit();
}
so, should I do this?
using (ITransaction transaction = session.BeginTransaction())
{
foreach (var item in records)
{
session.Delete(item);
}
try
{
transaction.Commit();
}
catch(Exception ex)
{
rollback,
session.dispose
session = factor.opensession()
}
}
First off, I use the second option all the time. Now to the question, disposing and opening a new session is practically painless so I usually don't mind doing it "if any error occurs".
I would like to use FileStream with Nhibernate.
The only post I found on stackoverflow is Sql 2008 Filestream with NHibernate
In the meantime, Nhibernate 3.0 (and 3.1) was released.
Does someone know a solution ?
It is not planned to be supported by nhibernate. (WONT FIX)
http://groups.google.com/group/nhusers/browse_thread/thread/e4a8f038f9c40a0d/a63ac4a8ce4f1244?pli=1
I hope it will change in the future ... maybe an extension (the open source magic)
Nhibernate can be used with the FILESTREAM... sort of.
Persist the object using Nhibernate as normal. You can then issue a couple of standard SQL Queries to take care of the filestream. I place the queries as named queries in my mapping file for the object.
public void Save(Photo photo){
//Save photo data
session.Save(photo);
//get path
String path;
Byte[] context;
IQuery qry1 = session.GetNamedQuery(QUERY_SET_BLANK);
qry1.SetInt64("photoId", photo.RID);
int cnt = qry1.ExecuteUpdate();
IQuery qry2 = session.GetNamedQuery(QUERY_GET_PATH);
qry2.SetInt64("photoId", photo.RID);
System.Collections.IList results = qry2.List();
object[] item = (object[]) results[0];
path = (String) item[0];
context = (Byte[])item[1];
if (context == null) throw new QueryException("Possible null transaction");
//save photo
using (SqlFileStream sqlFile = new SqlFileStream(path, context, FileAccess.Write)) {
photo.Image.Save(sqlFile, ImageFormat.Jpeg);
sqlFile.Close();
}
}
public Photo Get(Int64 rid) {
Photo result = session.Get<Photo>(rid);
if (result != null) {
IQuery qry = session.GetNamedQuery(QUERY_GET_PATH);
qry.SetInt64("photoId", result.RID);
System.Collections.IList results = qry.List();
object[] item = (object[])results[0];
var path = (String)item[0];
var context = (Byte[])item[1];
if (context == null) throw new QueryException("Possible null transaction");
using (SqlFileStream sqlFile = new SqlFileStream(path, context, FileAccess.Read))
result.Image = new System.Drawing.Bitmap(System.Drawing.Bitmap.FromStream(sqlFile));
}
return result;
}
Where Photo is an object mapped to a table. The actual column of the FILESTREAM type is not mapped to the object (excluded from mapping), the the named queries take care of the persistence for that column.
The name queries look like:
<sql-query name="PhotoPathContext">
select Photo.PathName() as path, GET_FILESTREAM_TRANSACTION_CONTEXT() as con
from Core.Photos where PhotoRID = :photoId
</sql-query>
<sql-query name="PhotoSetBlankFileStream">
update Core.Photos set Photo = Cast('' as varbinary(max)) where PhotoRID = :photoId
</sql-query>
The thing need to be wrapped in a transaction which is required by the GET_FILESTRAM_TRANSACTION_CONTEXT() method in the query.
Unit tests would look something like:
[Test]
public void TestSavePhoto() {
IList<Model.Photo> photos = repo.GetPhotos();
VegTabUtilityServices.Photo photo = new VegTabUtilityServices.Photo();
VegTabUtil.Model.Photo ph = photos[0];
photo.RowGuid = ph.GetGuid().Value;
photo.Name = ph.Name ?? photo.RowGuid.ToString();
photo.Image = ph.Image;
ISession session = SessionManager.Instance.Session;
PhotoService ps = new PhotoService(session);
using (NHibernate.ITransaction tx = session.BeginTransaction()) {
ps.Save(photo);
tx.Commit();
}
Assert.Greater(photo.RID, 0);
}
[Test]
public void TestPhotoConnection() {
ISession session = SessionManager.Instance.Session;
PhotoService ps = new PhotoService(session);
Photo p;
using (NHibernate.ITransaction tx = session.BeginTransaction()) {
p = ps.Get(6l);
tx.Commit();
}
Assert.NotNull(p);
Assert.NotNull(p.Image);
logger.Debug(String.Format("{0} by {1} pixels", p.Image.Width, p.Image.Height));
}
I have a more complete article on codeproject
The following code demonstrates a misleading situation in which data
is committed to the database, even though commit is never called on a
transaction.
Could anyone explain why?
[TestFixture]
public class TestFixture
{
[Test]
public void Test()
{
var config = DoConfiguration();
using(var factory = config.BuildSessionFactory())
{
using (var session = factory.OpenSession())
{
CallSessionContext.Bind(session);
using(new TransactionScope())
{
using (session.BeginTransaction())
{
var myEntity = session
.CreateQuery("from myEntity")
.List<MyEntity>()[0];
myEntity.Name = "test name";
}
var myEntity2 = session
.CreateQuery("from myEntity")
.List<MyEntity>()[0];
myEntity2.Name = "test name";
session.Flush();
}
CallSessionContext.Unbind(factory);
}
}
}
}
Explicitly calling session.flush() is persisting your changes. Discussed in detail in this post