【问题标题】:How to manage SqlDataReaders in a data access layer?如何在数据访问层管理 SqlDataReader?
【发布时间】:2010-10-30 18:42:30
【问题描述】:

我正在尝试更好地处理解耦我的代码、代码重用等。

每次我想阅读一些行时,我都厌倦了输入以下内容:

using(SqlConnection conn = new SqlConnection(myConnString))
{
  using(SqlCommand cmd = new SqlCommand(cmdTxt, conn))
  {
    conn.Open();
    using(SqlDataReader rdr = cmd.ExecuteReader())
    {
       while(rdr.Read())
       {
          /* do something with rows */
       }
     }
   }
}

我知道有 LINQ to SQL(我不喜欢它)和实体框架(还是个婴儿)。我不必输入我的查询没有问题,我只是不想每次都输入命令构造、行迭代器等。

我环顾四周,发现了一些我认为对我有用的东西,并尝试实施它以使事情变得更容易。正如您在评论中看到的,我收到 SqlDataReader 已关闭的错误。我猜这可能是因为 DataFactory.ExecuteReader() 方法中的 using 语句。返回阅读器时,对我的 SqlConnection 和 SqlCommand 变量调用 dispose 方法。我在吗?如果是这样,应该如何管理连接和命令变量?

编辑:我更新了我的代码示例以更好地反映我在做什么。

public class DataFactory
{
    public DataFactory()
    {}

    public DataFactory(string connectionString)
    {
       _connectionString = connectionString;
    }

    protected _connectionString = "Data Source=Localhost, etc, etc";
    private string ConnectionString
    {
        get{return _connectionString;}
    }

    public SqlConnection GetSqlConnection()
    {
        return new SqlConnection(ConnectionString);
    }

    public SqlDataReader ExecuteReader(string cmdTxt)
    {
        using(SqlConnection conn = new SqlConnection(ConnectionString))
        {
           using(SqlCommand cmd = new SqlCommand(cmdTxt, conn))
           {
                conn.Open();
                return cmd.ExecuteReader();
           }
        }
    }
}

public IRepository<T>
{
    T GetById(int id);
}

public MyTypeRepository: IRepository<MyType>
{
   private static DataFactory _df = new DataFactory();

   public MyType GetById(int id)
   {
        string cmdTxt = String.Format("SELECT Name FROM MyTable WHERE ID = {0}", id);

        using(SqlDataReader rdr = _df.ExecuteReader(cmdTxt))
        {
            if(rdr.Read()) /* I get an error that the reader is already closed here */
            {
                return new MyType(
                    Convert.ToInt32(rdr["Id"]),
                    rdr["Name"]);
            }
            else
            {
                return null;
            }
        }        
    }
}




public class MyType
{
    public MyType(int id, string name)
    {
      _id = id;
      _name = name;
    }

    private string _name;
    public string Name
    {
       get{return _name;}
    }

    private int _id;
    public int Id
    {
        get{return _id;}
    }

    public override void ToString()
    {
        return string.Format("Name: {0}, Id: {1}", Name, Id);
    }
}

public class Program
{
    private static MyTypeRepository _mtRepo = new MyTypeRepository();

    static void Main()
    {
        MyType myType = _mtRepo.GetById(1);

        Console.WriteLine(myType.ToString());
    }
}

我也想知道我正在做的事情是否有意义,或者,如果没有,如何实现类似的东西,这样我就不必经常输入连接创建等。

【问题讨论】:

  • 好吧 linq-2-sql 让调用存储过程变得非常简单,我猜每个人都有自己的想法。

标签: c# data-access-layer


【解决方案1】:

您的方法 ExecuteReader 将在返回 Reader 之前关闭连接。相反,它应该实现如下:

public IDataReader ExecuteReader(string cmdTxt)    
{        
    SqlConnection conn = new SqlConnection(...);
    try
    {
        SqlCommand cmd = new SqlCommand(cmdTxt, conn);
        conn.Open();                
        return cmd.ExecuteReader(CommandBehavior.CloseConnection);           
    }
    catch
    {
        conn.Close();
        throw;
    }
}

ExecuteReader 方法的调用者需要处理 IDataReader:

using(IDataReader reader = ExecuteReader(commandText))
{
    ...
} // reader will be disposed here and will close the connection.

请注意,上面没有调用 SqlCommand 对象上的 Dispose。根据我的经验,并通过使用 Reflector 查看 SqlCommand,只要 SqlConnection 被处置,就没有必要。但我相信如果您确实想处理它,以下方法会起作用:

public IDataReader ExecuteReader(string cmdTxt)    
{        
    SqlConnection conn = new SqlConnection(...);
    SqlCommand cmd = null;
    try
    {
        cmd = new SqlCommand(cmdTxt, conn);
        conn.Open();                
        IDataReader reader = 
            cmd.ExecuteReader(CommandBehavior.CloseConnection);           
        cmd.Dispose();
        return reader;
    }
    catch
    {
        if (cmd != null) cmd.Dispose();
        conn.Close();
        throw;
    }
}

【讨论】:

  • 在阅读器上调用 dispose 也会关闭 SqlConnection 吗?它是否也调用了 SqlCommand 上的 dispose?
  • 只要你像上面那样指定CommandBehavior.CloseConnecction,它就会关闭连接。我添加了更多关于未处理的 SqlCommand,但我相当确定不需要。
  • 从 MSDN,调用 IDataReader.Close(true) 将释放所有托管和非托管资源(我假设包括 SqlConnection 和 SqlCommand)。我认为这是我正在寻找的方法
  • "来自 MSDN,调用 IDataReader.Close(true)..." - 我的 MSDN 没有 IDataReader.Close(bool),但调用 IDataReader.Close() 将释放提供的非托管资源您使用 CommandBehavior.CloseConnection 打开了阅读器。
  • @Joe, ... DbDataReader().Close(bool),对不起。
【解决方案2】:

在使用数据阅读器后关闭和/或处置数据阅读器非常重要,然后每个想要使用 DataFactory 的人都应该记住这样做。我认为返回 DataTable 而不是 SqlDataReader 是一个好主意,这样你的 DataFactory不依赖于 SqlDataReader。

我的意思是:

public DataTable ExecuteReader(string cmdTxt)
    {
        using(SqlConnection conn = new SqlConnection(ConnectionString))
        {
           using(SqlCommand cmd = new SqlCommand(cmdTxt, conn))
           {
                conn.Open();
                using(SqlDataReader reader=cmd.ExecuteReader())
                {
                    DataTable dt=new DataTable();
                    dt.Load(reader);
                    return dt;
                }

           }
        }
    }

编辑: 好点。我也不喜欢数据表(我们使用 NHibernate,所以我实际上不在我们的应用程序中使用数据表) 因此,如果您想将数据读取器映射到您自己的对象,也许您可​​以拥有一个数据映射器,将数据读取器映射到您自己的对象,我的意思是:

public T[] ExecuteReader<T>(string cmdTxt)
    {
        using(SqlConnection conn = new SqlConnection(ConnectionString))
        {
           using(SqlCommand cmd = new SqlCommand(cmdTxt, conn))
           {
                conn.Open();
                using(SqlDataReader reader=cmd.ExecuteReader())
                {
                    var result=new List<T>();
                    while(reader.Read())
                         result.Add(ObjectMapper.MapReader<T>(reader));

                    return result.ToArray();
                }

       }
    }
}

【讨论】:

  • 我真的不喜欢 DataTables。对我来说,这只是很多开销。如果我只是拉出包含 4 或 5 个字段的单行,我宁愿只读取这 4 或 5 个字段并将它们放入我自己的类型中。
【解决方案3】:

我所做的是使用我的查询创建一个 XML 文件,并使用 XSLT 转换来生成我的 DAL 代码 CS 文件。您可以随心所欲,在 XML 中声明参数并在 XSLT 等中生成具有适当签名的方法等。我有一个博客条目,其中涵盖了相关主题,如何integrate the XSLT transformation into your Visual Studio project。现在有人可能会争辩说,使用类型化数据集是一回事,而且是免费午餐,但在我的例子中,我使用基于 BeginExecute/EndExecute 的异步 DAL。没有一个 VS 工具能正确使用这种方法,所以我基本上必须自己构建。

【讨论】:

    【解决方案4】:

    我会说它并没有真正解耦 - 基本上,您使用“使用 System.Data.SqlClient”的任何模块都与您的数据库耦合。 DAL 的全部意义在于应用程序与 DAL 耦合,而 DAL 与数据库耦合。

    【讨论】:

    • 嗯,这只是一个例子。在我正在开发的实际应用程序中,有一些存储库类实际上使用了 DataFactory 类。 “业务层”使用存储库类来获取对象。
    猜你喜欢
    • 2014-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-31
    • 1970-01-01
    • 2011-01-15
    • 2011-10-20
    相关资源
    最近更新 更多