【问题标题】:How to call a function that yields IEnumerable results?如何调用产生 IEnumerable 结果的函数?
【发布时间】:2026-02-15 14:35:02
【问题描述】:

这是我的服务器端代码,它正在执行 SQL 存储过程并向我发送一些表结果。

public IEnumerable<IDataRecord> SomeFuncToCallDB(string pName, SqlParameter[] sqlParams, int commandTimeout = -1)
    {
        using (SqlConnection cn = GetNewConnection())
        using (SqlCommand cmd = cn.CreateCommand())
        {
            cmd.CommandText = pName;
            cmd.CommandType = CommandType.StoredProcedure;
            if (commandTimeout != -1) cmd.CommandTimeout = commandTimeout;
            if (sqlParams.Length > 0) 
            { 
                FixNullParams(sqlParams); 
                cmd.Parameters.AddRange(sqlParams); 
            }
            cn.Open();
            using (SqlDataReader dr = cmd.ExecuteReader())
            {
                while (dr.Read())
                {
                    yield return (IDataRecord)dr;
                }
                cn.Close();
            }
        }
    }

这里是 FixNullParams 函数。

        private void FixNullParams(SqlParameter[] parameters)
    {
        foreach (SqlParameter p in parameters) if (p.Value == null) p.Value = DBNull.Value;
    }

什么时候,我这样调用这个函数,

                List<SqlParameter> parameters = new List<SqlParameter>();
            parameters.Add(new SqlParameter("@AgencyFilter", "CountyPool"));
            IEnumerable<IDataRecord> dataFromDB = salesTax.SomeFuncToCallDB("[Data].[uspSomeSQLProc]", parameters.ToArray());

我得到以下异常。

“SqlParameter 已被另一个 SqlParameterCollection 包含。”

这发生在 cmd.Parameters.AddRange 行。

同时,如果我这样调用同一个函数,

List<SqlParameter> parameters = new List<SqlParameter>();
            parameters.Add(new SqlParameter("@AgencyFilter", "CountyPool"));
            //IEnumerable<IDataRecord> dataFromDB = salesTax.SomeFuncToCallDB("[Data].[uspAgenciesSelect]", parameters.ToArray());
            foreach (IDataRecord eachRec in salesTax.SomeFuncToCallDB("[Data].[uspSomeSQLProc]", parameters.ToArray()))
            {
                int x = eachRec.FieldCount;

            }

然后它工作正常并循环遍历每条记录。

这是我的问题。

  1. 调用此过程的正确方法是什么?
  2. 如何在后面的调用中没有失败,但在前面的调用中失败了?
  3. 如果我必须为此函数编写单元测试,我只想获取 IEnumerable,然后断言记录数。但看起来,由于这个异常,我不能做这种测试。那么对这个函数进行单元测试的正确方法是什么?

【问题讨论】:

  • 那么你在哪里创建parameters?大概您正在使用已在其他地方使用的参数。请澄清您的问题,编辑代码部分,使它们尽可能易读(而不是缩进很长的一段距离),并在每个帖子中提出 一个 问题。
  • 可枚举的方法在编译时会生成一个状态机。我不能告诉你细节,但我认为这与它有关。如果有人知道那是 Jon Skeet。
  • @JonSkeet 我相信你是对的,OP 没有描述他的代码中包含问题的部分,只描述了错误被捕获的地方。我们需要您说明 SQL 参数类实例的位置和使用情况。 OP 正在重用一个 sql 参数。也许您使用的“FixNull”方法提供了相同的“空”参数,或者它返回的任何参数,这会使系统感到困惑,但老实说,我们无法通过您提供的代码确定错误的原因。
  • 我在代码中添加了缺失的信息。只是想知道呼叫如何以一种方式工作而以另一种方式失败。希望能学到新东西。
  • 迭代器方法的主体在 foreach 迭代之前不会进入,所以我很难相信只做第一个 sn-p 中显示的操作会抛出,而第二个不会。第二个必须执行与第一个相同的所有代码,然后执行一些。在您再次显示使用parameters 的位置之后,是否发生了什么事?

标签: c# .net ienumerable yield


【解决方案1】:

我想,我找到了问题所在。这是 Visual Studio 2017 中的调试行为

我能够创建一个小型测试数据库,然后创建一个简单的 Windows 应用程序来复制此问题。两个调用的行为相同。

但是,当我执行逐行调试操作时,这个异常就会出现,如下图所示。

对于好奇的用户,我在这里添加了完整的源代码。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Iterator
{
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        try
        {
            List<SqlParameter> parameters = new List<SqlParameter>();
            parameters.Add(new SqlParameter("@hobbyName", "Coding"));
            IEnumerable<IDataRecord> dataFromDB = GetUserInfo("dbo.GetUserBasedOnHobby", parameters.ToArray());
            int totalCount = dataFromDB.Count();
            MessageBox.Show("Total count : " + totalCount.ToString());
        }
        catch (Exception ex)
        {
            MessageBox.Show("Error : " + ex.ToString());
        }
    }

    private void button2_Click(object sender, EventArgs e)
    {
        try
        {
            List<SqlParameter> parameters = new List<SqlParameter>();
            parameters.Add(new SqlParameter("@hobbyName", "Coding"));
            List<IDataRecord> allRecs = new List<IDataRecord>();
            foreach (IDataRecord eachRec in GetUserInfo("dbo.GetUserBasedOnHobby", parameters.ToArray()))
            {
                allRecs.Add(eachRec);
            }
            int totalCount = allRecs.Count();
            MessageBox.Show("Total count : " + totalCount.ToString());
        }
        catch (Exception ex)
        {
            MessageBox.Show("Error : " + ex.ToString());
        }
    }

    private void FixNullParams(SqlParameter[] parameters)
    {
        foreach (SqlParameter p in parameters) if (p.Value == null) p.Value = DBNull.Value;
    }

    public IEnumerable<IDataRecord> GetUserInfo(string pName, SqlParameter[] sqlParams, int commandTimeout = -1)
    {
        using (SqlConnection cn = new SqlConnection("Data Source=MACHINE2;Initial Catalog=TestDB;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"))
        using (SqlCommand cmd = cn.CreateCommand())
        {
            cmd.CommandText = pName;
            cmd.CommandType = CommandType.StoredProcedure;
            if (commandTimeout != -1) cmd.CommandTimeout = commandTimeout;
            if (sqlParams.Length > 0)
            {
                FixNullParams(sqlParams);
                cmd.Parameters.AddRange(sqlParams);
            }
            cn.Open();
            using (SqlDataReader dr = cmd.ExecuteReader())
            {
                while (dr.Read())
                {
                    yield return (IDataRecord)dr;
                }
                cn.Close();
            }
        }
    }


  }
}

这是样本测试数据。

这是存储过程的 SQL 脚本。

USE [TestDB]
GO

/****** Object:  StoredProcedure [dbo].[GetUserBasedOnHobby]    Script Date: 12/9/2017 3:14:43 PM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE PROCEDURE [dbo].[GetUserBasedOnHobby]
@hobbyName varchar(50)
    AS
SELECT * from dbo.UserTable where dbo.UserTable.Hobby = @hobbyName;
    RETURN 0
    GO

感谢大家的 cmets 和回复。它帮助我深入挖掘以发现问题。

【讨论】:

    最近更新 更多