【问题标题】:Edit the values returned from an SQL Query编辑从 SQL 查询返回的值
【发布时间】:2021-12-24 01:51:27
【问题描述】:

对于我正在进行的项目,我有此 OwnershipRole 记录:

public record OwnershipRole
{

    public OwnershipRole()
    {

    }

    public OwnershipRole(Guid id, string title, bool isWithdrawn)
    {
        Id = id;
        Title = title;
        IsWithdrawn = isWithdrawn;
    }

    public Guid Id { get; set; }
    public string Title { get; set; }
    public bool IsWithdrawn { get; set; }
}
}

为了获取这些详细信息,我使用如下 sql 查询:

    public List<OwnershipRole> GetOwnershipRoles()
    {

        var sql = @"
SELECT ItemID, Title, Status
FROM ItemDetail";


        var data = GetTable(sql);

        return data.Select().Select(dr => new OwnershipRole
        {
            Id = dr.Field<Guid>(0),
            Title = dr.Field<string>(1),
            IsWithdrawn = !dr.Field<bool>(2)
        }).ToList();
    }

    private DataTable GetTable(string sql)
    {
        var rv = new DataTable();
        using var cnn = new SqlConnection(_connectionString);
        using var cmd = new SqlCommand(sql, cnn);
        cnn.Open();

        var da = new SqlDataAdapter(cmd);
        da.Fill(rv);
    
        cnn.Close();
        return rv;
    }

我们正在获取结果并将它们放入数据表中。但是,我遇到的问题是 Status 列中的值实际上是字符串。所以我想知道是否可能有类似的情况:

if (Status == "Withdrawn") {
Status == false

}

我知道这行不通,但有没有办法可以操纵从 Status 列获得的值,以适应 OwnershipRole 类中的 bool 参数?

【问题讨论】:

  • 你的数据库中Status列的数据类型是什么?
  • 旁注:cnn.Close(); 是多余的,using var cnn = new SqlConnection(_connectionString); 将为您关闭cnn
  • GetTable() 方法真的让我害怕。它实际上迫使您编写极易受到 sql 注入攻击的代码。
  • @DmitryBychenko cnn.Open() 也是多余的,因为SqlAdapter.Fill() 也会打开和关闭连接。
  • 你会如何改进它?这就是我得到的,只是任务扩展它

标签: c# sql


【解决方案1】:

当然,要么:

        var sql = @"
SELECT ItemID, Title, CASE WHEN Status='Withdrawn' then 1 else 0 end IsWithdrawn
FROM ItemDetail";

    return data.Select().Select(dr => new OwnershipRole
    {
        Id = dr.Field<Guid>(0),
        Title = dr.Field<string>(1),
        IsWithdrawn = dr.Field<string>(2)=="Withdrawn"?true:false
    }).ToList();

【讨论】:

  • 不需要三元运算符:IsWithdrawn = dr.Field&lt;string&gt;(2) != "Withdrawn"
  • 效果很好,谢谢。知道这很简单,但就是想不通,谢谢:)
  • data.Cast&lt;DataRow&gt;() 效率更高,因为它不会创建新数组
【解决方案2】:

是的,你可以很好地从 RDBS 读取string,然后从中得到bool Status(摆脱GetTable 方法):

  public List<OwnershipRole> GetOwnershipRoles() {
    using var cnn = new SqlConnection(_connectionString);

    cnn.Open();

    string sql =
      @"SELECT ItemID, 
               Title, 
               Status
          FROM ItemDetail";

    using var cmd = new SqlCommand(sql, cnn);

    var result = new List<OwnershipRole>(); 

    using var reader = cmd.OpenReader();

    while (reader.Read()) { 
      result.Add(new OwnershipRole(
        Guid.Parse(Convert.ToString(reader[0])),
        Convert.ToString(reader[1]),
        Convert.ToString(reader[2]) != "Withdrawn" 
      )); 
    }

    return result; 
  }

在这里

 Convert.ToString(reader[2]) != "Withdrawn" 

我们将最后一个字段 (Status) 读取为 string,然后通过与 "Withdrawn" 进行比较将其转换为 bool

【讨论】:

    【解决方案3】:

    来自cmets:

    GetTable() 方法真的让我害怕。它实际上迫使您编写极易受到 sql 注入攻击的代码。

    你会如何改进它?

    我会这样做:

    private DataTable GetTable(string sql, SqlParameter[] paras = null)
    {
        var rv = new DataTable();
        using var cnn = new SqlConnection(_connectionString);
        using var cmd = new SqlCommand(sql, cnn);
        using var da = new SqlDataAdapter(cmd);
        if (paras != null && paras.Length > 0)
        {
            cmd.Parameters.AddRange(paras);
        }
    
        da.Fill(rv);
        return rv;
    }
    
    private IEnumerable<IDataRecord> GetRows(string sql, SqlParameter[] paras = null)
    {
        using var cnn = new SqlConnection(_connectionString);
        using var cmd = new SqlCommand(sql, cnn);
        if (paras != null && paras.Length > 0)
        {
            cmd.Parameters.AddRange(paras);
        }
        cnn.Open();
        using var rdr = cmd.ExecuteReader();
        while (rdr.Read())
        {
            yield return rdr;
        }
    }
    
    private IEnumerable<T> GetItems<T>(string sql, Func<IDataRecord, T> transform, SqlParameter[] paras = null)
    {
        using var cnn = new SqlConnection(_connectionString);
        using var cmd = new SqlCommand(sql, cnn);
        if (paras != null && paras.Length > 0)
        {
            cmd.Parameters.AddRange(paras);
        }
        cnn.Open();
        using var rdr = cmd.ExecuteReader();
        while (rdr.Read())
        {
            yield return transform(rdr);
        }
    }
    

    第一种方法按原样工作。如果确实没有参数,您甚至不需要对简单查询使用附加参数。其他两种方法的工作方式如下:

    public IEnumerable<OwnershipRole> GetOwnershipRoles()
    {
        var sql = @"
    SELECT ItemID, Title, Status
    FROM ItemDetail";
    
        var data = GetRows(sql);
        return data.Select(dr => new OwnershipRole
            {
                Id = (Guid)dr[0],
                Title = (string)dr[1],
                IsWithdrawn = ((string)dr[2])!="Withdrawn"
            });
    }
    

    像这样:

    public IEnumerable<OwnershipRole> GetOwnershipRoles()
    {
        var sql = @"
    SELECT ItemID, Title, Status
    FROM ItemDetail";
    
        return GetItems<OwnershipRole>(sql, row => new OwnershipRole
           {
               Id = (Guid)dr[0],
               Title = (string)dr[1],
               IsWithdrawn = ((string)dr[2])!="Withdrawn"
           });
    }
    

    注意返回类型从List 更改为IEnumerable。一个好的经验法则是尽可能长时间地将内容保留为IEnumerable。这可以减少内存使用并为您设置流式值,因此在某些情况下,您根本不需要内存中的整个结果集。很快我们就会想使用IAsyncEnumerable

    当您确实需要一个列表时(这比您想象的要少),您可以在调用该方法之后添加ToList() 调用。尤其要小心在 GetRows() 函数上调用 ToList(),因为它会在每次迭代中产生相同的对象。做错了会给你一堆代表最后一行的记录。

    最后,如果你不知道如何使用 SqlParameter 对象,停止并去谷歌,因为这是一个非常重要的交易 .

    【讨论】:

    • 这是一个很好的解释,是的,我一定会对此进行更多研究。感谢您花时间解释
    • 如果您已经在内存中缓冲了结果,我不会返回 IEnumerable。调用者必须防御性地调用 .ToList(),它会复制集合,以防它是封装 DataReader 的流集合。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-01
    • 2013-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多