【问题标题】:How to read values from stored procedure?如何从存储过程中读取值?
【发布时间】:2020-12-15 20:17:05
【问题描述】:

我正在尝试从我的 .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 块中退出时,连接及其关联的读取器都将被关闭。
  • 您需要保持连接和阅读器处于活动状态(即未处置),直到您完成阅读阅读器。 DataReaders 和 using 语句并不总是很好的匹配。
  • @Steve 感谢您的精彩解释!我怎么能避免这种情况,你能帮我取悦史蒂夫吗?谢谢!
  • 通常 DataReader 不会从创建它的方法中传递出去。我通常避免使用它(数据读取器)并用像 Dapper 这样的良好 ORM 替换它,它会自动将我的查询转换为我的模型中定义的对象列表。我认为这应该是解决您问题的方法

标签: c# sql-server stored-procedures .net-core sqldatareader


【解决方案1】:

当代码从包含连接的 using 块中退出时,连接及其关联的读取器都将关闭。
这意味着 DataReader 通常不太适合在方法之间传递。存在一些解决方法,see here for example,但我发现完全放弃 SqlDataReader 和 SqlCommand 并用像 Dapper 这样的简单 ORM 替换它们更有用

现在,假设您有一个 User 类,其中包含一些与数据表字段名称完全匹配的属性。

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;
}

当然消息类中的Data字段应该是User类型,或者如果你使用多种不同的类型,你甚至可以将列表序列化为Json字符串并返回

【讨论】:

  • 我已经使用 dapper 很长时间了,这是一个不错的选择,但也要考虑 RepoDB,它允许类似的事情 + 有更多的玩具让生活更轻松。 repodb.net
  • 这似乎是一个很好的选择。我会把它放在我的雷达上,谢谢。
【解决方案2】:

尝试在两个 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; -- 你有这个两次。没有伤害任何东西,但也没有帮助。

您真的应该寻找一种更优雅的方式来进行数据访问。直接的 ADO.NET 笨重且难以使用。探索 Entity Framework 和 Dapper 等选项。

【讨论】:

  • 在 using 语句中移动 return 不会改变任何内容。即使您 return 在 using 中,阅读器也会关闭
  • 啊射击。 @史蒂夫是对的。自从我与 ADO 合作以来已经太久了。我编辑了我的答案。另外,请查看阅读器上的文档:docs.microsoft.com/en-us/dotnet/api/…
【解决方案3】:

我认为使用 Dapper 之类的东西会更开心,但是,如果你想遵循直接的 SqlConnection/DataReader 路径,请考虑使用类似这个简单的包装类的东西:

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 并返回它,而不是返回 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);
         }
     }
 }

【讨论】:

  • 好主意,但我感觉 OP 正试图通过 webapi 将数据读取器传递给客户端代码。 (.... 在我的 .NET Core Web API 上。) 如果这是上下文,那么包装阅读器并不能解决问题
  • 是的,我同意。这是一个想法。在许多情况下,这绝对不是要走的路。但是,当您接受 DataReaders 的想法(有时确实会发生)时,它是一个轻量级的解决方案。我最近使用类似的东西是从表名(没有类型,只有表名)创建 JSON 文件。您为表的 SELECT * 语句创建读取器,从读取器获取元数据,构建 Dictionary&lt;string, object&gt;,其中每个对象代表一行,然后 JSON 序列化字典。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多