代码之家  ›  专栏  ›  技术社区  ›  Joel Coehoorn

在using语句中从datalayer返回datareader

  •  10
  • Joel Coehoorn  · 技术社区  · 16 年前

    我们有很多数据层代码遵循这个非常一般的模式:

    public DataTable GetSomeData(string filter)
    {
        string sql = "SELECT * FROM [SomeTable] WHERE SomeColumn= @Filter";
    
        DataTable result = new DataTable();
        using (SqlConnection cn = new SqlConnection(GetConnectionString()))
        using (SqlCommand cmd = new SqlCommand(sql, cn))
        {
            cmd.Parameters.Add("@Filter", SqlDbType.NVarChar, 255).Value = filter;
    
            result.Load(cmd.ExecuteReader());
        }
        return result;
    }
    

    我想我们可以做得更好一点。我现在的主要抱怨是它强制所有的记录都被载入内存,即使是对于大的记录集。我希望能够利用DataReader每次只在RAM中保存一条记录的能力,但是如果我直接返回DataReader,那么在离开使用块时,连接会被切断。

    我该如何改进这一点以允许一次返回一行?

    5 回复  |  直到 7 年前
        1
  •  13
  •   Joel Coehoorn    12 年前

    再一次,为这个问题撰写思想的行为揭示了答案。具体来说,我写的最后一句话是“一次一行”。我意识到我并不在乎它是一个数据阅读器,只要我能一行一行地枚举它。这让我想到:

    public IEnumerable<IDataRecord> GetSomeData(string filter)
    {
        string sql = "SELECT * FROM [SomeTable] WHERE SomeColumn= @Filter";
    
        using (SqlConnection cn = new SqlConnection(GetConnectionString()))
        using (SqlCommand cmd = new SqlCommand(sql, cn))
        {
            cmd.Parameters.Add("@Filter", SqlDbType.NVarChar, 255).Value = filter;
            cn.Open();
    
            using (IDataReader rdr = cmd.ExecuteReader())
            {
                while (rdr.Read())
                {
                    yield return (IDataRecord)rdr;
                }
            }
        }
    }
    

    当我们移到3.5并且可以开始在结果上使用其他LINQ操作符时,这会更好地工作,我喜欢它,因为它使我们开始考虑在每个层之间为返回大量结果的查询建立一个“管道”。

    不利的一面是,对于持有多个结果集的读者来说,这会很尴尬,但这是非常罕见的。

    更新
    自从2009年我第一次开始使用这种模式以来,我已经了解到,如果我也将其作为通用模式,那就更好了。 IEnumerable<T> 返回类型并添加 Func<IDataRecord, T> 参数,用于将数据读取器状态转换为循环中的业务对象。否则,惰性迭代可能会有问题,这样每次都会看到查询中的最后一个对象。

        2
  •  7
  •   Henk Holterman    9 年前

    你想要的是一个受支持的模式,你必须使用

    cmd.ExecuteReader(CommandBehavior.CloseConnection);
    

    并删除两者 using() 形成getSomeData()方法。例外安全必须由调用者提供,以确保接近读卡器。

        3
  •  3
  •   Peter Lillevold Rene    7 年前

    在这样的时候,我发现羊羔肉有很大的用处。考虑到这一点,不要让数据层给我们提供数据,而是让我们给数据层我们的数据处理方法:

    public void GetSomeData(string filter, Action<IDataReader> processor)
    {
        ...
    
        using (IDataReader reader = cmd.ExecuteReader())
        {
            processor(reader);
        }
    }
    

    然后业务层会称之为:

    GetSomeData("my filter", (IDataReader reader) => 
        {
            while (reader.Read())
            {
                ...
            }
        });
    
        4
  •  2
  •   Community CDub    8 年前

    关键是 yield 关键字。

    与乔尔最初的回答类似,更为充实的是:

    public IEnumerable<S> Get<S>(string query, Action<IDbCommand> parameterizer, 
                                 Func<IDataRecord, S> selector)
    {
        using (var conn = new T()) //your connection object
        {
            using (var cmd = conn.CreateCommand())
            {
                if (parameterizer != null)
                    parameterizer(cmd);
                cmd.CommandText = query;
                cmd.Connection.ConnectionString = _connectionString;
                cmd.Connection.Open();
                using (var r = cmd.ExecuteReader())
                    while (r.Read())
                        yield return selector(r);
            }
        }
    }
    

    我有一个扩展方法:

    public static void Parameterize(this IDbCommand command, string name, object value)
    {
        var parameter = command.CreateParameter();
        parameter.ParameterName = name;
        parameter.Value = value;
        command.Parameters.Add(parameter);
    }
    

    所以我称之为:

    foreach(var user in Get(query, cmd => cmd.Parameterize("saved", 1), userSelector))
    {
    
    }
    

    这是完全通用的,适用于符合ADO.NET接口的任何模型。 The connection and reader objects are disposed after the collection is enumerated. 总之,填充 DataTable 使用 IDataAdapter Fill 方法 can be faster than DataTable.Load

        5
  •  0
  •   John    16 年前

    我从来都不喜欢让数据层返回一个通用的数据对象,因为这很大程度上解决了将代码分离到它自己的层的整个问题(如果没有定义接口,如何切换数据层?).

    我认为您的最佳选择是,对于类似这样的所有函数,返回您自己创建的自定义对象的列表,并且在以后的数据中,将过程/查询调用到DataReader,并迭代创建列表。

    这将使一般情况下更容易处理(尽管创建自定义类的初始时间),使处理连接更容易(因为您不会返回任何与之关联的对象),而且应该更快。唯一的缺点是所有的东西都会像你提到的那样被载入内存,但是我不认为这会引起关注(如果是的话,我认为需要调整查询)。