1

As learning exercise and before trying to use any ORM (like EF) I want to build a personal project using ADO.NET and stored procedures.

Because I don't want my code to become a mess over time, I want to use some patterns like the repository and UoW patterns.

I've got almost everything figured it out, except for the transaction handling.

To somehow 'simulate' a UoW, I used this class provided by @jgauffin, but what's stopping me from using that class is that every time you create a new instance of that class (AdoNetUnitOfWork) you're automatically beginning a transaction and there a lot of cases where you only need to read data.

In this regard this is what I found in one of the SQL books I've been reading:

Executing a SELECT statement within a transaction can create locks on the referenced tables, which can in turn block other users or sessions from performing work or reading data

This is the AdoNetUnitOfWork class:

public class AdoNetUnitOfWork : IUnitOfWork
{
    public AdoNetUnitOfWork(IDbConnection connection, bool ownsConnection)
    {
        _connection = connection;
        _ownsConnection=ownsConnection;
        _transaction = connection.BeginTransaction();
    }

    public IDbCommand CreateCommand()
    {
        var command = _connection.CreateCommand();
        command.Transaction = _transaction;
        return command;
    }

    public void SaveChanges()
    {
        if (_transaction == null)
            throw new InvalidOperationException("Transaction have already been commited. Check your transaction handling.");

        _transaction.Commit();
        _transaction = null;
    }

    public void Dispose()
    {
        if (_transaction != null)
        {
            _transaction.Rollback();
            _transaction = null;
        }

        if (_connection != null && _ownsConnection)
        {
            _connection.Close();
            _connection = null;
        }
    }
}

And this is how I want to use the UoW in my repositories:

public DomainTable Get(int id)
{
    DomainTable table;

    using (var commandTable = _unitOfWork.CreateCommand())
    {
        commandTable.CommandType = CommandType.StoredProcedure;
        //This stored procedure contains just a simple SELECT statement
        commandTable.CommandText = "up_DomainTable_GetById";

        commandTable.Parameters.Add(commandTable.CreateParameter("@pId", id));

        table = ToList(commandTable).FirstOrDefault();
    }

    return table;
}

I know I can tweak this code a bit so that the transaction would be optional, but since I trying to make this code as platform independent as possible and as far as I know in other persistence frameworks like EF you don't have to manage transactions manually, the question is, will I be creating some kind of bottleneck by using this class as it is, that is, with transactions always being created?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
eddy
  • 4,373
  • 16
  • 60
  • 94
  • 1
    http://dba.stackexchange.com/questions/43254/is-it-a-bad-practice-to-always-create-a-transaction https://social.msdn.microsoft.com/Forums/sqlserver/en-US/18a09050-79a8-4c83-99e6-1335a14604e0/why-wrap-just-a-select-statement-in-a-transaction?forum=transactsql – Vladimir Baranov Oct 22 '16 at 23:50

1 Answers1

6

It all depends on the transaction isolation level. Using the default isolation level (ie. read committed) then your SELECT should occur no performance penalty if is wrapped in a transaction. SQL Server internally wraps statements in a transaction anyway if one is not already started, so your code should behave almost identical.

However, I must ask you why not use the built-in .Net TransactionScope? This way your code will interact much better with other libraries and frameworks, since TransactionScope is universally used. If you do decide to switch to this I must warn you that, by default, TransactionScope uses SERIALIZABLE isolation level and this does result in performance penalties, see using new TransactionScope() Considered Harmful.

David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
Remus Rusanu
  • 288,378
  • 40
  • 442
  • 569
  • Thank you @Remus. I've also seen being mentioned in other questions that, **by default** SQL internally creates a transaction for every statement but I haven't been able to find any source confirming that. Regarding you question why I haven't used `TransactionScope`, well because in the little time I've been reading about databases and c# it is the first time that I've heard of it (I'm a front-end developer trying to make his way into the back-end world), but it sounds interesting. – eddy Oct 23 '16 at 18:14
  • I know that every project is different, but based on you experience, which type of isolation level has proven to be the most useful to you or the one you usually default to whenever you start a new project? – eddy Oct 23 '16 at 18:19
  • I leave it at read committed unless I have specific reasons to change it. – Remus Rusanu Oct 23 '16 at 18:36
  • Also, if you just begun looking at back-end C# app server development then you should consider returning `IQueryable` instead of a list. An `IQueryable` can further be refined after is returned from the repository, and the actual database query is delayed until the data is actually needed. See http://softwareengineering.stackexchange.com/questions/192044/should-repositories-return-iqueryable , https://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application – Remus Rusanu Oct 23 '16 at 18:41
  • Thanks @Remus. I'll leave it as read committed and thanks also for the tip about using `IQueryable`. In my repositories I'm not returning List, instead I'm returning IEnumerable. IIRC `IQueryable` will prove to be extremely useful when I start using an ORM like EF because it offers Deferred Execution. That `ToList` you saw in my repository is part of an extension method that uses reflection to map every column in my queries to properties in my classes, at least whenever possible. – eddy Oct 23 '16 at 20:37
  • Do you think you could refer me to any source explaining that part about _"SQL Server internally wraps statements in a transaction if one is not already started"_? I can't find anything on the Internet , but I keep reading it everywhere :( and finally (sorry for asking too many questions), do you know if it is possible to see those _implicit_ transactions using a tool like **SQL Server Profiler**. So far I learned that explicit transactions started from a client application (like .net ) are those prefixed with `TM:` – eddy Oct 23 '16 at 20:56