NHibernate: At what scope I should use transaction?

房东的猫 提交于 2019-12-20 06:48:46

问题


I am developing Data Access Layer using NHibernate. It will be used in my Business Logic Layer. My application is collection of multiple other applications (ASP.NET MVC, Windows Services, Windows Forms, ASP.NET Web API) those all will use same Business Logic Layer. Business Logic Layer will access Data Access Layer. Applications will NOT access Data Access Layer directly.

I am aware that I should NOT depend on implicit transaction and should include all database calls (including READ calls) in explicit transaction.

In my understanding, transaction must be short lived. If it lives for long, it may create issues. Refer 1 and 2.

Many resources (on StackOverflow and other sites) suggest enclosing transaction code in using block. This really makes sense because it helps using explicit transaction for each database call. It also help making transaction short lived. Problem with this approach is that, batching is limited to using block only. UnitOfWork is not fully utilized.

On other hand, many resources (on StackOverflow and other sites) suggest using transaction on some higher level like web-request (session per request) in web application OR "per windows form" in WinForms etc. This improves batching of queries and help taking better advantage of UnitOfWork pattern. Problem with this approach is that, transactions are long lived.

Way 1] Expose transaction to application and let it handle it.

Option 1 - Caller can handle this at request level. Drawback of this approach is that, transactions may be long lived.

Option 2 - If he does not do so, the only way is to handle it at lower level like "per method" or "per small block of code". But now, he has to make sure he begins the transaction and commit/rollback it properly. This reduces readability, is difficult to code-review and debug in case of problem. Also, if this has to be at lower level anyway, why not include this in Business Logic Layer instead?

Way 2] Control transaction inside Business Logic Layer.

This makes all transactions short lived. But this does not take good advantage of batching or UnitOfWork. BLL may not know in advance how database calls will be made by calling application.

What is recommended location to Begin and Commit transaction which will take good advantage of batching and honoring UnitOfWork?

Edit 1: (After accepting answer)

I just found excellent article on UnitOfWork. Following are some of the extracts from the same:

Do not use the session-per-operation antipattern: do not open and close a Session for every simple database call in a single thread. The same is true for database transactions. Database calls in an application are made using a planned sequence; they are grouped into atomic units of work. This also means that auto-commit after every single SQL statement is useless in an application as this mode is intended for ad-hoc SQL console work.

Database transactions are never optional. All communication with a database has to occur inside a transaction. Auto-commit behavior for reading data should be avoided, as many small transactions are unlikely to perform better than one clearly defined unit of work. The latter is also more maintainable and extensible.

The most common pattern in a multi-user client/server application is session-per-request. In this model, a request from the client is sent to the server, where the Hibernate persistence layer runs. A new Hibernate Session is opened, and all database operations are executed in this unit of work. On completion of the work, and once the response for the client has been prepared, the session is flushed and closed. Use a single database transaction to serve the clients request, starting and committing it when you open and close the Session. The relationship between the two is one-to-one and this model is a perfect fit for many applications.

The session-per-request pattern is not the only way of designing units of work. Many business processes require a whole series of interactions with the user that are interleaved with database accesses. In web and enterprise applications, it is not acceptable for a database transaction to span a user interaction.

Database, or system, transaction boundaries are always necessary. No communication with the database can occur outside of a database transaction (this seems to confuse many developers who are used to the auto-commit mode). Always use clear transaction boundaries, even for read-only operations. Depending on your isolation level and database capabilities this might not be required, but there is no downside if you always demarcate transactions explicitly. Certainly, a single database transaction is going to perform better than many small transactions, even for reading data.


回答1:


Why not make the service(s) transaction aware?

something like

    protected virtual TResult Transact<TResult>(Func<TResult> func)
    {
        if (_session.Transaction.IsActive)
            return func.Invoke();

        TResult result;
        using (var tx = _session.BeginTransaction(IsolationLevel.ReadCommitted))
        {
            result = func.Invoke();
            tx.Commit();
        }

        return result;
    }

    protected virtual void Transact(System.Action action)
    {
        Transact(() =>
        {
            action.Invoke();
            return false;
        });
    }

in your service or repo that checks if a transaction is active and participates in an active one or creates a new transaction if non exists.

Now you can compose multiple service calls together under one transcation instead of having each call in isolation.



来源:https://stackoverflow.com/questions/41301400/nhibernate-at-what-scope-i-should-use-transaction

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!