5

我想在我的 WinForms 应用程序中实现 N 层架构,以将业务逻辑与数据访问分开(只是逻辑上 - 在一个项目中),但是我对在 BLL 中使用事务有一些疑问。我在 Internet 上找到的所有教程要么是该架构的非常简单的实现(没有事务),要么对于我的需要来说太复杂了。试图找到自己的方式,我已经到了这一点,我不知道在 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))

在 DAL 中。要使用 NpgsqlTransacion 对象,我必须以某种方式在两个 DAL 类中保持连接打开和可见。
我已经尝试为此使用TransacionScope对象,但由于某些原因,它不能与 PostgreSQL 和我正在使用的驱动程序一起使用(INSERTS 是在执行后完成的,并且当 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.CloseConnecion() 中处理连接吗?使用(NpgsqlConnection conn = ...) { ... }模式时我肯定会这样做,但是由于单例只要应用程序就存在,这有意义吗?Connection 在 之后返回到 ConnectionPool Close(),不是吗?或者,也许我还应该在每次使用后处理一个 Singleton 对象(连同连接)?
3.这不是直接相关的问题,但是如果我使用 DTO 对象(只是属性,没有方法)并且还有一些具有相同属性的 BusinessObjects (BO),而且还有其他方法(验证、计算、操作等),它可以是继承自 DTO?或者也许我可以使用完整的 BusinessObject 在层之间传输它,并摆脱 DTO?

编辑:TransacionScope
根据要求,我从我的 TransactionScope 尝试中添加了一些代码。简单的 WinForm 应用程序,没有异常处理。结果,当我抛出它时会出现一个异常窗口,但在数据库中我看到值为 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();
        }
    }
}
4

2 回答 2

2

你所做的很勇敢,但不可扩展。我不熟悉 PGSQL,但这个问题正是设计 TransactionScope API 的原因。

您可以使用 TransactionScope api 显示您的代码吗?确保您没有调用 scope.Complete(); 如果其中一种方法发生错误。小心不要“吃掉”方法中的异常,因为在这种情况下,流程将继续,就像什么都没发生一样。

更多关于 TransactionScope 的阅读:http: //msdn.microsoft.com/en-us/library/ms172152.aspx

更新 1

感谢您分享使用 TransactionScope 类的代码。代码对我来说看起来完全正确。根据此(http://npgsql.projects.postgresql.org/docs/manual/UserManual.html)文档(由 ChrisNeil52 引用的相同内容),Enlist=true 应包含在连接字符串中以使事务正常工作。

您可能正在处理有缺陷的 API。祝你好运。

我知道这听起来很奇怪,但我会尝试使用不同的 NpgsqlCommand 构造函数。new NpgsqlCommand("sql query", connection),而不是创建命令并为其分配连接。它们应该是等价的。但谁知道...

于 2012-06-28T12:44:28.850 回答
2

我从未使用过 NpgSql,但阅读 NpgSql 的文档后,如果您在连接字符串中添加“enlist=true” ,它们似乎对TransactionScope()有一些支持。

我正在查看以下 NpgSql 文档的“System.Transactions 支持”部分:http: //npgsql.projects.postgresql.org/docs/manual/UserManual.html

假设 TransactionScope() 确实有效,那么你可以做这样的事情......

using (var scope = new System.Transactions.TransactionScope())
{
        ItemDAL itemDAL = new ItemDAL(); 
        SomeOtherItemDAL someOtherItemDAL = new SomeOtherItemDAL(); 

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

       scope.Complete()
}
于 2012-06-28T14:28:41.933 回答