Я начал внедрять веб-страницу asp.net с использованием NHibernate
в качестве сопоставления базы данных.
Я следую этому руководству, но мне не удается заставить код работать.
Ниже приведен мой сценарий, который я изменил в абстрактном классе.
Не могли бы вы сообщить мне, если это будет правильно?
Я добавил метод this.CommitChanges();
для всех методов Save
, SaveOrUpdate
и Delete
.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using NHibernate;
using NHibernate.Criterion;
using Tec.Core.DataInterfaces;
using Tec.Data.SessionManagement;
namespace Tec.Data
{
public abstract class NHibernateRepository<T, IdT> : IRepository<T, IdT>
{
public T GetById(IdT id, bool shouldLock)
{
T entity;
if (shouldLock)
{
entity = (T)NHibernateSession.Load(persitentType, id, LockMode.Upgrade);
}
else
{
entity = (T)NHibernateSession.Load(persitentType, id);
}
return entity;
}
public List<T> GetAll()
{
return GetByCriteria();
}
public List<T> GetByCriteria(params ICriterion[] criterion)
{
ICriteria criteria = NHibernateSession.CreateCriteria(persitentType);
foreach (ICriterion criterium in criterion)
{
criteria.Add(criterium);
}
return criteria.List<T>() as List<T>;
}
public List<T> GetByExample(T exampleInstance, params string[] propertiesToExclude)
{
ICriteria criteria = NHibernateSession.CreateCriteria(persitentType);
Example example = Example.Create(exampleInstance);
foreach (string propertyToExclude in propertiesToExclude)
{
example.ExcludeProperty(propertyToExclude);
}
criteria.Add(example);
return criteria.List<T>() as List<T>;
}
public T GetUniqueByExample(T exampleInstance, params string[] propertiesToExclude)
{
List<T> foundList = GetByExample(exampleInstance, propertiesToExclude);
if (foundList.Count > 1)
{
throw new NonUniqueResultException(foundList.Count);
}
if (foundList.Count > 0)
{
return foundList[0];
}
else
{
return default(T);
}
}
public T Save(T entity) {
NHibernateSession.Save(entity);
//this.CommitChanges(); // manually added
return entity;
}
public T SaveOrUpdate(T entity)
{
NHibernateSession.SaveOrUpdate(entity);
//this.CommitChanges();
return entity;
}
public void Delete(T entity) {
NHibernateSession.Delete(entity);
//this.CommitChanges(); //Record is delete from database only if I enable this
statement
}
public void CommitChanges(){
if (NHibernateSessionFactory.Instance.HasOpenTransaction()) {
NHibernateSessionFactory.Instance.CommitTransaction();
} else {
// If there no transaction, just flush the changes
NHibernateSessionFactory.Instance.GetSession().Flush();
}
}
private ISession NHibernateSession
{
get
{
return NHibernateSessionFactory.Instance.GetSession();
}
}
private Type persitentType = typeof(T);
}
//----------------------------------------------------------------------------------//
public class NHibernateSessionFactory
{
public static NHibernateSessionFactory Instance
{
get
{
return Nested.NHibernateSessionFactory;
}
}
private NHibernateSessionFactory()
{
InitSessionFactory();
}
private class Nested
{
static Nested() { }
internal static readonly NHibernateSessionFactory NHibernateSessionFactory = new NHibernateSessionFactory();
}
private void InitSessionFactory()
{
sessionFactory = new Configuration().Configure().BuildSessionFactory();
}
public void RegisterInterceptor(IInterceptor interceptor)
{
ISession session = ContextSession;
if (session != null && session.IsOpen)
{
throw new CacheException("You cannot register an interceptor once a session has already been opened");
}
GetSession(interceptor);
}
public ISession GetSession()
{
return GetSession(null);
}
private ISession GetSession(IInterceptor interceptor)
{
ISession session = ContextSession;
if (session == null)
{
if (interceptor != null)
{
session = sessionFactory.OpenSession(interceptor);
}
else
{
session = sessionFactory.OpenSession();
}
ContextSession = session;
}
return session;
}
public void CloseSession()
{
ISession session = ContextSession;
if (session != null && session.IsOpen)
{
session.Flush();
session.Close();
}
ContextSession = null;
}
public void BeginTransaction()
{
ITransaction transaction = ContextTransaction;
if (transaction == null)
{
transaction = GetSession().BeginTransaction();
ContextTransaction = transaction;
}
}
public void CommitTransaction()
{
ITransaction transaction = ContextTransaction;
try
{
if (HasOpenTransaction())
{
transaction.Commit();
ContextTransaction = null;
}
}
catch (HibernateException)
{
RollbackTransaction();
throw;
}
}
public bool HasOpenTransaction()
{
ITransaction transaction = ContextTransaction;
return transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack;
}
public void RollbackTransaction()
{
ITransaction transaction = ContextTransaction;
try
{
if (HasOpenTransaction())
{
transaction.Rollback();
}
ContextTransaction = null;
}
finally
{
CloseSession();
}
}
private ITransaction ContextTransaction
{
get
{
if (IsInWebContext())
{
return (ITransaction)HttpContext.Current.Items[TRANSACTION_KEY];
}
else
{
return (ITransaction)CallContext.GetData(TRANSACTION_KEY);
}
}
set
{
if (IsInWebContext())
{
HttpContext.Current.Items[TRANSACTION_KEY] = value;
}
else
{
CallContext.SetData(TRANSACTION_KEY, value);
}
}
}
private ISession ContextSession
{
get
{
if (IsInWebContext())
{
return (ISession)HttpContext.Current.Items[SESSION_KEY];
}
else
{
return (ISession)CallContext.GetData(SESSION_KEY);
}
}
set
{
if (IsInWebContext())
{
HttpContext.Current.Items[SESSION_KEY] = value;
}
else
{
CallContext.SetData(SESSION_KEY, value);
}
}
}
private bool IsInWebContext()
{
return HttpContext.Current != null;
}
private const string TRANSACTION_KEY = "CONTEXT_TRANSACTION";
private const string SESSION_KEY = "CONTEXT_SESSION";
private ISessionFactory sessionFactory;
}
}
Вот мой метод тестирования:
public void AddUser()
{
// create three people
User jose = new User();
jose.UserName = "Jose";
jose.UserLogin = "28";
mUser.SaveOrUpdate(jose);// Record is addded to database,
// some people said it
// because on auto-increment identity
User maria = new User();
maria.UserName = "Maria";
maria.UserLogin = "29";
mUser.SaveOrUpdate(maria);
User mario = new User();
mario.UserName = "Mario";
mario.UserLogin = "27";
mUser.SaveOrUpdate(mario);
// delete Mario
mUser.Delete(mario); //Record is not deleted from database
}
и я получил только эти 3 инструкции, выполненные на профилировщике SQL
exec sp_reset_connection
exec sp_executesql N'INSERT INTO TEC.dbo.tblSysUser (UserName, UserLogin) VALUES (@p0, @p1); select SCOPE_IDENTITY()',N'@p0 nvarchar(4000),@p1 nvarchar(4000)',@p0=N'Jose',@p1=N'28'
exec sp_reset_connection
exec sp_executesql N'INSERT INTO TEC.dbo.tblSysUser (UserName, UserLogin) VALUES (@p0, @p1); select SCOPE_IDENTITY()',N'@p0 nvarchar(4000),@p1 nvarchar(4000)',@p0=N'Maria',@p1=N'29'
exec sp_reset_connection
exec sp_executesql N'INSERT INTO TEC.dbo.tblSysUser (UserName, UserLogin) VALUES (@p0, @p1); select SCOPE_IDENTITY()',N'@p0 nvarchar(4000),@p1 nvarchar(4000)',@p0=N'Mario',@p1=N'27'
Заявление после un-comment this.CommitChanges() на Delete Methode
exec sp_reset_connection
exec sp_executesql N'INSERT INTO TEC.dbo.tblSysUser (UserName, UserLogin) VALUES (@p0, @p1); select SCOPE_IDENTITY()',N'@p0 nvarchar(4000),@p1 nvarchar(4000)',@p0=N'Jose',@p1=N'28'
exec sp_reset_connection
exec sp_executesql N'INSERT INTO TEC.dbo.tblSysUser (UserName, UserLogin) VALUES (@p0, @p1); select SCOPE_IDENTITY()',N'@p0 nvarchar(4000),@p1 nvarchar(4000)',@p0=N'Maria',@p1=N'29'
exec sp_reset_connection
exec sp_executesql N'INSERT INTO TEC.dbo.tblSysUser (UserName, UserLogin) VALUES (@p0, @p1); select SCOPE_IDENTITY()',N'@p0 nvarchar(4000),@p1 nvarchar(4000)',@p0=N'Mario',@p1=N'27'
exec sp_reset_connection
exec sp_executesql N'DELETE FROM TEC.dbo.tblSysUser WHERE UserID = @p0',N'@p0 bigint',@p0=64
Не могли бы вы сообщить, что происходит в моем коде? Я что-то пропустил?
Я бы сказал: Код, показанный в вопросе, приводит к неправильным, нежелательным результатам. Но проблема не в стороне NHibernate (например, некоторая ошибка в реализации).
Проблема в том, что каждый набор операций (веб-запрос, блок работы) должен быть инкапсулирован в трансконте - с явным вызовом Commit
или Rollback
.
NHibernate иногда просто должен выполнить некоторые операционные состояния WRITE * (см. 9.6. Документация по флешу :)
... объекты, использующие встроенную идентификацию, вставляются, когда они сохраняются...
Тот факт, что NHibernate должен выдать этот вызов, не означает, что такая операция должна сохраняться. В этом случае это только результат неизбежного - получить идентификатор сгенерированной БД. Это все еще может случиться, что мы должны вернуться к работе позже...
Наиболее важным параметром является FlushMode
ISession
* (ISession
из 9.6. Документация по ISession
:)
... За исключением случаев, когда вы проявляете
Flush()
, абсолютно никаких гарантий о том, когда сеанс выполняет вызовы ADO.NET, - это только тот порядок, в котором они выполняются. Однако NHibernate гарантирует, чтоISession.Find(..)
никогда не возвратят устаревшие данные; и они не вернут неверные данные.
Итак, именно то, что мы испытали выше - FlushMode установлен в Auto... нет явной транзакции Commit или Rollback, но данные сохраняются. Но это был скорее всего несчастный случай... из-за отсутствия транзакции
Здесь доступны FlushModes (см. Цитату, фрагмент кода ниже)
/// <summary>
/// Represents a flushing strategy.
/// </summary>
/// <remarks>
/// The flush process synchronizes database state with session state by detecting state
/// changes and executing SQL statements
/// </remarks>
[Serializable]
public enum FlushMode
{
/// <summary>
/// Special value for unspecified flush mode (like <see langword="null" /> in Java).
/// </summary>
Unspecified = -1,
/// <summary>
/// The <c>ISession</c> is never flushed unless <c>Flush()</c> is explicitly
/// called by the application. This mode is very efficient for read only
/// transactions
/// </summary>
Never = 0,
/// <summary>
/// The <c>ISession</c> is flushed when <c>Transaction.Commit()</c> is called
/// </summary>
Commit = 5,
/// <summary>
/// The <c>ISession</c> is sometimes flushed before query execution in order to
/// ensure that queries never return stale state. This is the default flush mode.
/// </summary>
Auto = 10,
/// <summary>
/// The <see cref="ISession"/> is flushed before every query. This is
/// almost always unnecessary and inefficient.
/// </summary>
Always = 20
}
Когда мы создаем сеанс с фабрики, мы можем назначить один из этих FlushModes. Я настоятельно рекомендую использовать FlushMode.Commit
или - использовать FlusMode.Never и вызвать Session.Flush() непосредственно перед транзакцией Commit().
Потому что - мы должны быть драйвером явного вызова Flush()
. Итак, не полагайтесь на то, что NHiberante выпустил INSERT. Это было необходимо для последующей обработки. Всегда завершайте набор операций в Transacton, используйте FlushMode.Commit... это будет мое предложение
ПРИМЕЧАНИЕ. Также проверьте это. Управление транзакциями criteria.List(Type), чтобы узнать больше о преимуществах транзакций даже для операций чтения.