【问题标题】:Most Efficient Way To Get A Row Of Data From DB In ASP.NET在 ASP.NET 中从数据库中获取一行数据的最有效方法
【发布时间】:2010-10-19 22:12:42
【问题描述】:

我正在编写一个从数据库返回“资产”行的方法。它包含字符串、整数和字节数组(这可能是图像/电影/文档)。

现在对于大多数行访问,我使用以下方法,它返回 NameValueCollection,因为它是一个轻量级对象,易于使用并转换为 int 和字符串。

        public static NameValueCollection ReturnNameValueCollection(Database db, DbCommand dbCommand)
    {

        var nvc = new NameValueCollection();

        using (IDataReader dr = db.ExecuteReader(dbCommand))
        {
            if (dr != null)
            {
                 while (dr.Read())
                 {
                     for (int count = 0; count < dr.FieldCount; count++)
                     {
                         nvc[dr.GetName(count)] = dr.GetValue(count).ToString();
                     }
                 }
            }
        }

        dbCommand.Dispose();
        return nvc.Count != 0 ? nvc : null;
    }

现在我对这种数据访问的方法通常是获取返回数据行的方法。

       public static DataRow ReturnDataRow(Database db, DbCommand dbCommand)
    {
        var dt = new DataTable();

        using (IDataReader dr = db.ExecuteReader(dbCommand))
            if (dr != null) dt.Load(dr);

        dbCommand.Dispose();
        return dt.Rows.Count != 0 ? dt.Rows[0] : null;
    }

创建一个 DataTable 然后返回它的第一个数据行似乎有点浪费。

有更好的方法吗?

我在想可能是一个对象字典,然后我手动转换每个成员。

看看其他人如何解决这个问题会很有趣。我知道这有点属于微优化领域,只要我不为每个行查询返回数据集(希望我每次在一行代码中看到它都有一磅)它应该没问题。

也就是说,这个方法可能会被调用来分配一个盒子上的网站上的数据访问查询。

干杯

史蒂夫

【问题讨论】:

  • 只是一个小问题,但调用者应该调用 DbCommand.Dispose 方法,而不是您的数据读取方法。由于您的调用者提供了 dbCommand 对象,它应该处理它。

标签: c# asp.net optimization data-access


【解决方案1】:

您要演示的是一种名为Primitive Obsession 的代码气味。创建一个自定义类型并从您的存储库方法中返回它。不要试图过于通用......您最终只会将这种复杂性推入您的业务代码,因为您将使用纯程序代码与您的实体进行交互。最好创建为您的业务建模的对象。

如果您担心数据访问代码过多,请考虑使用 ORM 框架来为您生成此代码。您不应该让这种担忧决定您的应用程序层的糟糕设计。

【讨论】:

    【解决方案2】:

    就代码效率而言,您可能用最少的按键就完成了它,虽然看起来很浪费,但可能是最容易维护的。但是,如果您只关心只做严格必要的事情的效率,您可以创建一个轻量级的结构/类来填充数据并使用类似的东西:

    public class MyAsset
    {
        public int ID;
        public string Name;
        public string Description;
    }
    
    public MyAsset GetAsset(IDBConnection con, Int AssetId)
    {
        using (var cmd = con.CreateCommand("sp_GetAsset"))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.Add(cmd.CreateParameter("AssetID"));
            using(IDataReader dr = cmd.ExecuteReader())
            {
                if (!dr.Read()) return null;
    
                return new MyAsset() { 
                    ID = dr.GetInt32(0), 
                    Name = dr.GetString(1), 
                    Description = dr.GetString(2)
                };
            }
        }
    }
    

    同样,您可以以类似的方式将数据直接转储到您的 KVP 集合中...

    它看起来不像您的原始代码那么干净,但它不会创建整个表只是为了获取单行...

    正如在另一篇关于代码异味的帖子中提到的那样,我可能不会将命令作为参数传递,我认为我更有可能将命令封装在此方法中,仅传递数据库连接和我想要的资产的 id - 假设我当然没有使用缓存,并传回 MyAsset 实例。这使得该方法足够通用,可以在任何数据库类型上使用 - 当然假设存储过程存在。这样,我的其余代码就不需要知道数据库的任何类型,而不是它是什么类型的数据库......并且在我的应用程序的其余部分,我可以使用 MyAssetInstance.ID、MyAssetInstance.Name、 MyAssetInstance.Description 等...

    【讨论】:

      【解决方案3】:

      与尝试优化返回单行相比,您将从缓存数据中获得更多好处。如果您通过主键进行选择,那么您不太可能会看到返回 DataTable 或 DataRow 或自定义对象之间的任何区别。这让我觉得过早的优化。我会更确定,但我不确定混合中的字节数组是否会改变事情。

      【讨论】:

        【解决方案4】:

        怎么样了?

        您没有代表数据库中一行的对象容器是否有原因?在解决方案的其他层中创建自定义对象要容易得多。因此,采用这种方法,有两种非常可行的解决方案可以解决您的问题。

        假设您有一个自定义对象,该对象代表数据库中的产品。你可以这样定义对象:

        public class Product {
            public int ProductID { get; set; }
            public string Name { get; set; }
            public byte[] Image { get; set; }
        }
        

        你会像这样填写一组产品(集合):

        var collection = new Collection<Product>();
        
        using (var reader = command.ExecuteReader()) {
            while (reader.Read()) {
                var product = new Product();
        
                int ordinal = reader.GetOrdinal("ProductID");
                if (!reader.IsDBNull(ordinal) {
                    product.ProductID = reader.GetInt32(ordinal);
                }
        
                ordinal = reader.GetOrdinal("Name");
                if (!reader.IsDBNull(ordinal)) {
                    product.Name = reader.GetString(ordinal);
                }
        
                ordinal = reader.GetOrdinal("Image");
                if (!reader.IsDBNull(ordinal)) {
                    var sqlBytes = reader.GetSqlBytes(ordinal);
                    product.Image = sqlBytes.Value;
                }
        
                collection.Add(product);
            }
        }
        

        请注意,我正在通过阅读器的 Getx 检索一个值,其中 x 是我要从列中检索的类型。这是 Microsoft 推荐的根据 http://msdn.microsoft.com/en-us/library/haa3afyz.aspx(第二段)检索列数据的方法,因为检索到的值不必装箱到 System.Object 中并取消装箱到原始类型中。

        由于您提到此方法将在 ASP.NET 应用程序中被调用很多次,您可能需要重新考虑这样的通用方法。您用来返回 NameValueCollection 的方法在这种情况下(并且可以说在许多其他情况下)表现不佳。更不用说在不考虑当前用户的文化的情况下将每个数据库列转换为字符串,而文化是 ASP.NET 应用程序中的一个重要考虑因素。我认为这个 NameValueCollection 也不应该用于您的其他开发工作。我可以继续说下去,但我会为你省去我的咆哮。

        当然,如果您要创建直接映射到表的对象,您不妨研究一下LINQ to SQLADO.NET Entity Framework。你会很高兴你这样做了。

        【讨论】:

        • +1 因为我以前从未注意到 reader.Getxxx 方法,这是一个很棒的提示!
        【解决方案5】:

        感谢所有输入的家伙。我知道 ORM 可能是要走的路,而 MVC 框架在我的列表中是下一个。

        为了提供更多细节,我展示的代码来自数据访问层中的帮助器部分,然后将行或名称值集合传递给业务层以转换为对象。

        我认为 mnero0429 和 balabaster 代码示例给了我正确的方向。使用数据读取器并像这样手动获取数据,而不会弄乱中间对象。感谢您提供详细的 MS 链接 mnero0429。对原始的痴迷很公平-尽管我确实在业务层中从中做出了适当的资产类别;)

        我还将研究 ADO 实体框架。

        再次感谢您的建议 - 我知道即使我使用 DataSet.Tables[0].Rows[0]["bob"] 或类似的东西,世界也会继续转动,但是当您感到痒时 - 是什么最好的方法,它很高兴被划伤!

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-09-06
          • 2018-07-21
          • 2017-08-10
          • 1970-01-01
          • 1970-01-01
          • 2021-01-18
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多