Sie sollten Ihre gesamte Arbeitseinheit abwickeln. Arbeitseinheit sollte die Arbeit abdecken, die Sie tun, um Ihre Ansichtsmodelle zu holen.
Dadurch wird faul-Laden vermeiden aus einer Transaktion erfolgen.
Es erlaubt Ihnen auch die komplette Arbeitseinheit im Fehlerfall rückgängig zu machen.
Und es hat einen Leistungsvorteil: Einer der Gründe für NHProfiler Warnung ist die Kosten für das Öffnen einer Transaktion für jeden Datenzugriff. (Es gibt andere, z. B. den Cache der zweiten Ebene, der eine explizite Transaktion erfordert, andernfalls wird er bei Aktualisierungen deaktiviert.)
Sie können die verwenden, die Sie gefunden haben.
Ich persönlich finde es auch "breit". Es enthält in der Transaktion sogar die Ergebnisausführung. Dies ermöglicht die Verwendung von Lazy-Loading in Ansichten. Ich denke, wir sollten keine Entitäten als Ansichtsmodelle verwenden, und das Auslösen des DB-Zugriffs aus Ansichten ist für mich noch schlimmer. Die eine, die ich benutze, beendet die Transaktion in OnActionExecuted
.
Darüber hinaus sieht seine Fehlerbehandlung für mich etwas spezifisch aus. Rollback bei ungültigem Modellstatus führt möglicherweise nicht zu sens: keine Aktion soll versuchen, ungültige Daten in der DB zu speichern. Das Rückgängigmachen der Rollback-Funktion für behandelte Ausnahmen ist weniger seltsam: Wenn die MVC-Pipeline eine Ausnahme festgestellt hat, bedeutet dies, dass bei der Aktion oder beim Ausführen des Ergebnisses ein Fehler aufgetreten ist. Einige andere Filter haben jedoch die Ausnahme behandelt. Normalerweise ist nur ein Fehlerfilter, der eine Fehlerseite anzeigt, nicht wahr? Dann verursacht eine solche Logik eine fehlgeschlagene Aktion ...
Hier wird das Muster des einen ist die ich benutze:
public class DefaultTransactionAttribute : ActionFilterAttribute
{
private static readonly ILog Logger =
LogManager.GetLogger(typeof(DefaultTransactionAttribute));
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// IUnitOfWork is some kind of custom ISession encapsulation.
// I am working in a context in which we may change the ORM, so
// I am hiding it.
var uow = DependencyResolver.Current.GetService<IUnitOfWork>();
uow.BeginTransaction();
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
var uow = DependencyResolver.Current.GetService<IUnitOfWork>();
if (!uow.HasActiveTransaction())
{
// Log rather than raise an exception, for avoiding hiding
// another failure.
Logger.Warn("End of action without a running transaction. " +
"Check how this can occur and try avoid this.");
return;
}
if (filterContext.Exception == null)
{
uow.Commit();
}
else
{
try
{
uow.Rollback();
}
catch(Exception ex)
{
// Do not let this new exception hide the original one.
Logger.Warn("Rollback failure on action failure. (If the" +
"transaction has been roll-backed on db side, this is" +
"expected.)", ex);
}
}
}
}
Für eine Schritt für Schritt Erklärung eines minimal MVC-Muster, diese große Blog-Serie von Ayende:
- Refactoring, baseline
- Refactoring, session scope
- Refactoring, broken
- Refactoring, view model
- Refactoring, globals
- Refactoring, transactions
Da meine IUnitOfWork
einige spezielle Semantik hat, die mir in dem MVC-Muster, das ich mit NHibernate verwenden helfen, hier ist sie:
// This contract is not thread safe and must not be shared between threads.
public interface IUnitOfWork
{
/// <summary>
/// Save changes. Generally unneeded: if a transaction is ongoing,
/// its commit does it too.
/// </summary>
void SaveChanges();
void CancelChanges();
bool HasActiveTransaction();
void BeginTransaction();
/// <summary>
/// Saves changes and commit current transaction.
/// </summary>
void Commit();
void Rollback();
/// <summary>
/// Encapsulate some processing in a transaction, committing it if
/// no exception was sent back, roll-backing it otherwise.
/// The <paramref name="action"/> is allowed to rollback the transaction
/// itself for cancelation purposes. (Commit supported too.)
/// Nested calls not supported (InvalidOperationException). If the
/// session was having an ongoing transaction launched through direct
/// call to <c>>BeginTransaction</c>, it is committed, and a new
/// transaction will be opened at the end of the processing.
/// </summary>
/// <param name="action">The action to process.</param>
void ProcessInTransaction(Action action);
/// <summary>
/// Encapsulate some processing in a transaction, committing it if
/// no exception was sent back, roll-backing it otherwise.
/// The <paramref name="function"/> is allowed to rollback the transaction
/// itself for cancellation purposes. (Commit supported too.)
/// Nested calls not supported (InvalidOperationException). If the
/// session was having an ongoing transaction launched through direct
/// call to <c>>BeginTransaction</c>, it is committed, and a new
/// transaction will be opened at the end of the processing.
/// </summary>
/// <param name="function">The function to process.</param>
/// <typeparam name="T">Return type of
/// <paramref name="function" />.</typeparam>
/// <returns>The return value of the function.</returns>
T ProcessInTransaction<T>(Func<T> function);
}
public class UnitOfWork : IUnitOfWork
{
private static readonly ILog Logger =
LogManager.GetLogger(typeof(UnitOfWork));
private ISession Session;
public UnitOfWork(ISession session)
{
Session = session;
}
public void SaveChanges()
{
Session.Flush();
}
public void CancelChanges()
{
Session.Clear();
}
public bool HasActiveTransaction()
{
return Session.Transaction.IsActive;
}
public void BeginTransaction()
{
Session.BeginTransaction();
}
public void Commit()
{
Session.Transaction.Commit();
}
public void Rollback()
{
Session.Transaction.Rollback();
}
public void ProcessInTransaction(Action action)
{
if (action == null)
throw new ArgumentNullException("action");
ProcessInTransaction<object>(() =>
{
action();
return null;
});
}
private bool _processing = false;
public T ProcessInTransaction<T>(Func<T> function)
{
if (function == null)
throw new ArgumentNullException("function");
if (_processing)
throw new InvalidOperationException(
"A transactional process is already ongoing");
// Handling default transaction.
var wasHavingActiveTransaction = Session.Transaction.IsActive;
if (wasHavingActiveTransaction)
Commit();
BeginTransaction();
T result;
_processing = true;
try
{
result = function();
}
catch
{
try
{
if(Session.Transaction.IsActive)
Rollback();
}
catch (Exception ex)
{
// Do not let this new exception hide the original one.
Logger.Error("An additional error occurred while " +
"attempting to rollback a transaction after a failed " +
"processing.", ex);
}
// Let original exception flow untouched.
throw;
}
finally
{
_processing = false;
}
if (Session.Transaction.IsActive)
Commit();
if (wasHavingActiveTransaction)
BeginTransaction();
return result;
}
}
Welche Methode gibt genau dieses NHProf-Problem? Ihr Repository sieht gut aus und die Verwendung einer Delegat-Wrapper-Lösung scheint einwandfrei zu funktionieren. –
Entschuldigung, alle meine Select-Methode wird in Transaktion in FooController Beispiel aufgerufen, aber zum Beispiel, wenn Sie Foo.Bar wo Bar Verweis auf foo Objekt in meinem Controller ist, wird die Bar-Auswahl aus Transaktion – Arnaud