如何从存储过程中读取值?

How to read values from stored procedure?

我正在尝试从我的 .NET Core Web 上的存储过程中读取值 API。

这是得到的响应:

System.InvalidOperationException: Invalid attempt to call FieldCount when reader is closed.
   at Microsoft.Data.SqlClient.SqlDataReader.get_FieldCount()
   at System.Data.Common.DbEnumerator.BuildSchemaInfo()
   at System.Data.Common.DbEnumerator.MoveNext()
   at System.Text.Json.JsonSerializer.HandleEnumerable(JsonClassInfo elementClassInfo, JsonSerializerOptions options, Utf8JsonWriter writer, WriteStack& state)
   at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.JsonSerializer.WriteAsyncCore(Stream utf8Json, Object value, Type inputType, JsonSerializerOptions options, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
   at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

这是我的代码:

try
{
    using (SqlConnection con = new SqlConnection(myConnString))
    {
        using (SqlCommand cmd = new SqlCommand("GetUsers", con)) // Simple proc which returning all 'child' users
        {
            cmd.CommandType = CommandType.StoredProcedure;

            cmd.Parameters.Add("@parentUserId", SqlDbType.UniqueIdentifier).Value = parentUserId;

            // open connection to database
            con.Open();

            //set the SqlCommand type to stored procedure and execute
            cmd.CommandType = CommandType.StoredProcedure;

            SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);

           message.Data = reader;
        }
    }
    return message;
}
catch (Exception ex)
{
    message.IsValid = false;
}

当我调试这个时,我意识到数据存在于结果中,但它嵌套在另一个对象中,就像它在此处的图像上看起来的那样:

尝试在两个 using 语句中移动 return message;

try
{
    using (SqlConnection con = new SqlConnection(myConnString))
    {
        using (SqlCommand cmd = new SqlCommand("GetUsers", con)) // Simple proc which returning all 'child' users
        {
            cmd.CommandType = CommandType.StoredProcedure;

            cmd.Parameters.Add("@parentUserId", SqlDbType.UniqueIdentifier).Value = parentUserId;

            // open connection to database
            con.Open();

            //set the SqlCommand type to stored procedure and execute
            cmd.CommandType = CommandType.StoredProcedure;

            SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);

           while (reader.Read())
            {
                // add each line to message.Data
            }

           return message;
        }
    }
}
catch (Exception ex)
{
    message.IsValid = false;
}

您一离开 using,您的 reader 就丢失了。

你如何处理这个 // add each line to message.Data 将取决于 message.Data 的样子,我们无法判断。

此外,还有一些其他事情:

cmd.CommandType = CommandType.StoredProcedure; -- 你有两次。没有伤害任何东西,但也没有帮助。

您真的应该研究一种更优雅的数据访问方式。 Straight ADO.NET 笨重且难以使用。探索 Entity Framework 和 Dapper 等选项。

当代码从包含连接的 using 块中退出时,连接及其关联的 reader 都将关闭。
这意味着 DataReader 通常不适合在方法之间传递。存在一些解决方法,see here for example but I have found more useful to totally abandon the SqlDataReader and SqlCommand and replacing them with a simple ORM like Dapper

现在,假设您有一个用户 class,其某些属性与数据表字段的名称完全匹配。

public class User
{
     public Guid ID {get;set;}
     public string Name {get;set;}
     public string EMail {get;set;}
     public Guid ParentID {get;set;}
     ... etc ...
}

此时你的代码,使用 Dapper 将是

using (SqlConnection con = new SqlConnection(myConnString))
{
    con.Open();
    List<User> users = con.Query<User>("GetUsers",  
                       new {parentUserId=parentUserId}, 
                       commandType: CommandType.StoredProcedure).ToList();
    message.Data = users;
    return message;
}

当然消息中的字段数据 class 应该是用户类型,或者如果您使用许多不同的类型,您甚至可以将列表序列化为 Json 字符串和 return

我认为使用 Dapper 之类的东西你会更开心,但是,如果你想走直线 SqlConnection/DataReader路径,考虑像这样的简单包装器 class:

public class WrappedReader : IDisposable
{
    public SqlDataReader Reader { get; }
    public SqlConnection Connection { get; }
    public WrappedReader(SqlConnection connection, SqlDataReader reader)
    {
        Reader = reader;
        Connection = connection;
    }

    public void Dispose()
    {
        Reader?.Dispose();
        Connection?.Dispose();
    }

您构造一个 WrappedReader 和 return 而不是 return DataReader,类似于:

public static WrappedReader TestWrappedReader()
{
    var connection = new SqlConnection(connectionString);
    using (var command = new SqlCommand("Select * from SomeTable", connection))
    {
        var reader = command.ExecuteReader();
        return new WrappedReader(connection, reader);
    }
}

那么你可以这样称呼它:

 using (var wrappedReader = TestWrappedReader())
 {
     var reader = wrappedReader.Reader;
     if (reader.HasRows)
     {
         while (reader.Read())
         {
             DoSomethingWith(reader);
         }
     }
 }