N -обработка транзакций многоуровневой архитектуры

Я хотел бы реализовать N -многоуровневую архитектуру в своих приложениях WinForms, чтобы отделить (просто логически -в одном проекте )бизнес-логику от доступа к данным, однако у меня есть некоторые сомнения относительно использования транзакций в BLL. Все учебники, которые я нашел в Интернете, либо очень простые реализации этой архитектуры (без транзакций ), либо слишком сложны для моих нужд. Пытаясь найти свой собственный путь, я пришел к тому, что не знаю, как лучше всего обрабатывать транзакции на уровне BLL.
Я попытаюсь использовать простой пример, чтобы проиллюстрировать проблему (все классы находятся в отдельных файлах):

//DTO - Data Transfer Objects
public class Item
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class SomeOtherItem
{
    public int Id { get; set; }
    public string Name { get; set; }
}

//DAL - Data Access layer
public class ItemDAL
{
    public ItemDAL()
    {
    }

    public void Add(Item item)
    {
        using (NpgsqlConnection conn = new NpgsqlConnection(connString))
        {
            conn.Open();
            using (NpgsqlCommand cmd = new NpgsqlCommand())
            {
                cmd.Connection = conn;
                cmd.CommandText = @"INSERT INTO tbl_items (name)
                                    VALUES (@name)";
                cmd.Parameters.AddWithValue("@name", item.Name);
                cmd.ExecuteNonQuery();
            }
        }
    }
}

public class SomeOtherItemDAL
{
    public SomeOtherItemDAL()
    {
    }

    public void Add(SomeOtherItem someOtherItem)
    {
        using (NpgsqlConnection conn = new NpgsqlConnection(connString))
        {
            conn.Open();
            using (NpgsqlCommand cmd = new NpgsqlCommand())
            {
                cmd.Connection = conn;
                cmd.CommandText = @"INSERT INTO tbl_some_other_items (name)
                                    VALUES (@name)";
                cmd.Parameters.AddWithValue("@name", someOtherItem.Name);
                cmd.ExecuteNonQuery();
            }
        }
    }
}

//BLL - Business Logic Layer
public class SomeBLL
{
    public SomeBLL()
    {
    }

    public void Add(Item item, SomeOtherItem someOtherItem)
    {

        ItemDAL itemDAL = new ItemDAL();
        SomeOtherItemDAL someOtherItemDAL = new SomeOtherItemDAL();

        // *** this must be done in one transaction ***
        itemDAL.Add(item);
        someOtherItemDAL.Add(someOtherItem);
    }
}

Теперь проблема в том, что если я хочу использовать Transacion, я не могу использовать:

using (NpgsqlConnection conn = new NpgsqlConnection(connString))

в ДАЛ. Чтобы использовать объект NpgsqlTransacion, я должен каким-то образом держать соединение открытым и видимым в обоих классах DAL.
Я пытался использовать для этого объект TransacionScope , но по некоторым причинам он не работает с PostgreSQL и драйвером, который я использую. (ВСТАВКИ выполняются сразу после выполнения, и при исключении отката транзакции не происходит внутри TransacionScope происходит ).

Я пришел к тому, чтобы создать дополнительный класс Singleton для поддержания соединения и управления транзакциями :

public class DB
{
    private static DB instance;
    private const string connString = @"Server=localhost;Port=5432;Database=db_test;User Id=usr_test;Password=pass";
    private NpgsqlConnection conn;

    private DB()
    {
        conn = new NpgsqlConnection(connString);
    }

    public static DB Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new DB();
            }
            return instance;
        }
    }

    #region --- connection ---
    public NpgsqlConnection GetOpenConnection()
    {
        OpenConnection();
        return conn;
    }

    private void OpenConnection()
    {
        if (conn.State == ConnectionState.Closed || conn.State == ConnectionState.Broken)
            conn.Open();
    }

    public void CloseConnection()
    {
        if (conn != null && !inTransaction)
        {
            conn.Close();
        }
    }
    #endregion

    #region --- transaction ---
    private NpgsqlTransaction trans;
    private bool inTransaction;
    public bool InTransaction { get { return inTransaction; } }

    public void TransactionStart()
    {
        OpenConnection();
        trans = conn.BeginTransaction();
        inTransaction = true;
    }

    public void TransactionCommit()
    {
        if (inTransaction)
        {
            try
            {
                trans.Commit();
                trans.Dispose();
            }
            finally
            {
                inTransaction = false;
                CloseConnection();
            }
        }
    }

    public void TransactionRollback()
    {
        if (inTransaction)
        {
            try
            {
                trans.Rollback();
                trans.Dispose();
            }
            finally
            {
                inTransaction = false;
                CloseConnection();
            }
        }
    }
    #endregion
}

. и перестройте оба метода DAL Add для доступа к такому соединению:

//DAL - Data Access layer
public class ItemDAL
{
    public ItemDAL()
    {
    }

    public void Add(Item item)
    {
        using (NpgsqlCommand cmd = new NpgsqlCommand())
        {
            cmd.Connection = DB.Instance.GetOpenConnection();
            cmd.CommandText = @"INSERT INTO tbl_items (name)
                                VALUES (@name)";
            cmd.Parameters.AddWithValue("@name", item.Name);
            cmd.ExecuteNonQuery();
        }
        if (!DB.Instance.InTransaction)
            DB.Instance.CloseConnection();
    }
}

public class SomeOtherItemDAL
{
    public SomeOtherItemDAL()
    {
    }

    public void Add(SomeOtherItem someOtherItem)
    {
        using (NpgsqlCommand cmd = new NpgsqlCommand())
        {
            cmd.Connection = DB.Instance.GetOpenConnection();
            cmd.CommandText = @"INSERT INTO tbl_some_other_items (name)
                                VALUES (@name)";
            cmd.Parameters.AddWithValue("@name", someOtherItem.Name);
            cmd.ExecuteNonQuery();
        }
        if (!DB.Instance.InTransaction)
            DB.Instance.CloseConnection();
    }
}  

Обратите внимание, что я хотел бы следовать правилу «закрыть соединение с базой данных как можно скорее», поэтому, когда метод Add вызывается без области транзакции, я бы хотел, чтобы он закрыл соединение.

Итак, последние вопросы:
1. Что вы об этом думаете, есть ли лучший способ справиться с этой проблемой, есть предложения?
2.Должен ли я удалить соединение в DB.CloseConnection ()? Я, конечно, делаю это при использовании шаблона (NpgsqlConnection conn =...) {... }, но поскольку Синглтон жив до тех пор, пока приложение, имеет ли это смысл? Соединение возвращается в ConnectionPool после Close(), не так ли? Или, может быть, я должен также удалять объект Singleton (вместе с соединением )после каждого использования?
3. Это не напрямую связанный вопрос, но если я использую объекты DTO (только свойства, без методов )и также имею несколько BusinessObjects (BO )с теми же свойствами, но еще и с дополнительными методами (проверки, расчеты, операции и т. д. ), может ли он быть унаследован от DTO? Или, может быть, я могу использовать полный BusinessObject для передачи его между слоями и избавиться от DTO?

РЕДАКТИРОВАТЬ :TransacionScope
В соответствии с просьбой я добавляю некоторый код из моих попыток с TransactionScope. Просто приложение WinForm, без обработки исключений. В результате, когда я бросаю его, появляется окно Exception, но в базе данных я вижу записи со значениями test1 и test2. Как при отладке в VS, так и при выполнении приложения из.exe

using Npgsql;
using System.Transactions;
//...

private void button1_Click(object sender, EventArgs e)
{
    using (System.Transactions.TransactionScope scope = new TransactionScope())
    {
        AddValue("test1");
        AddValue("test2");
        throw new Exception("bam!");
        AddValue("test3");
        scope.Complete();
    }
}

private void AddValue(string value)
{
    string connString = "Server=localhost;Port=5432;Database=db_test;User Id=usr_test;Password=pass";

    using (NpgsqlConnection conn = new NpgsqlConnection(connString))
    {
        conn.Open();
        using (NpgsqlCommand cmd = new NpgsqlCommand())
        {
            cmd.Connection = conn;
            cmd.CommandText = @"INSERT INTO tbl_test (name)
                                VALUES (@name)";
            cmd.Parameters.AddWithValue("@name", value);
            cmd.ExecuteNonQuery();
        }
    }
}
5
задан mj82 28 June 2012 в 13:02
поделиться