如何在数据访问层中管理SqlDataReaders?

时间:2009-06-02 18:27:51

标签: c# data-access-layer

我正在尝试更好地解决我的代码,代码重用等问题。

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

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(我不喜欢它)和实体框架(还是个孩子)。我没有问题必须输出我的查询,我只是不想每次都要输入命令contruction,row iterator等。

我环顾四周,发现了一些我觉得适合我的东西,并尝试实施它以使我更容易。正如您在注释中看到的,我收到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());
    }
}

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

4 个答案:

答案 0 :(得分: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();
                }

       }
    }
}

答案 1 :(得分:2)

您的方法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;
    }
}

答案 2 :(得分:1)

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

答案 3 :(得分:0)

我会说它并没有真正解耦 - 基本上你使用“使用System.Data.SqlClient”的任何模块都耦合到你的数据库。 DAL的重点是应用程序耦合到DAL,DAL耦合到数据库。