将SqlDataReader的结果映射到对象的最快方法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了将SqlDataReader的结果映射到对象的最快方法相关的知识,希望对你有一定的参考价值。

我正在比较Dapper与ADO.NET和Dapper之间的物化时间。 最终,Dapper往往比ADO.NET更快,尽管第一次执行给定的获取查询比ADO.NET慢。 一些结果表明Dapper比ADO.NET快一点(几乎所有的结果都表明它可以比较) 所以我认为我使用低效的方法将SqlDataReader的结果映射到对象。 这是我的代码

var sql = "SELECT * FROM Sales.SalesOrderHeader WHERE SalesOrderID = @Id";
        var conn = new SqlConnection(ConnectionString);
        var stopWatch = new Stopwatch();

        try
        {
            conn.Open();
            var sqlCmd = new SqlCommand(sql, conn);

            for (var i = 0; i < keys.GetLength(0); i++)
            {
                for (var r = 0; r < keys.GetLength(1); r++)
                {
                    stopWatch.Restart();
                    sqlCmd.Parameters.Clear();
                    sqlCmd.Parameters.AddWithValue("@Id", keys[i, r]);
                    var reader = await sqlCmd.ExecuteReaderAsync();
                    SalesOrderHeaderSQLserver salesOrderHeader = null;

                    while (await reader.ReadAsync())
                    {
                        salesOrderHeader = new SalesOrderHeaderSQLserver();
                        salesOrderHeader.SalesOrderId = (int)reader["SalesOrderId"];
                        salesOrderHeader.SalesOrderNumber = reader["SalesOrderNumber"] as string;
                        salesOrderHeader.AccountNumber = reader["AccountNumber"] as string;
                        salesOrderHeader.BillToAddressID = (int)reader["BillToAddressID"];
                        salesOrderHeader.TotalDue = (decimal)reader["TotalDue"];
                        salesOrderHeader.Comment = reader["Comment"] as string;
                        salesOrderHeader.DueDate = (DateTime)reader["DueDate"];
                        salesOrderHeader.CurrencyRateID = reader["CurrencyRateID"] as int?;
                        salesOrderHeader.CustomerID = (int)reader["CustomerID"];
                        salesOrderHeader.SalesPersonID = reader["SalesPersonID"] as int?;
                        salesOrderHeader.CreditCardApprovalCode = reader["CreditCardApprovalCode"] as string;
                        salesOrderHeader.ShipDate = reader["ShipDate"] as DateTime?;
                        salesOrderHeader.Freight = (decimal)reader["Freight"];
                        salesOrderHeader.ModifiedDate = (DateTime)reader["ModifiedDate"];
                        salesOrderHeader.OrderDate = (DateTime)reader["OrderDate"];
                        salesOrderHeader.TerritoryID = reader["TerritoryID"] as int?;
                        salesOrderHeader.CreditCardID = reader["CreditCardID"] as int?;
                        salesOrderHeader.OnlineOrderFlag = (bool)reader["OnlineOrderFlag"];
                        salesOrderHeader.PurchaseOrderNumber = reader["PurchaseOrderNumber"] as string;
                        salesOrderHeader.RevisionNumber = (byte)reader["RevisionNumber"];
                        salesOrderHeader.Rowguid = (Guid)reader["Rowguid"];
                        salesOrderHeader.ShipMethodID = (int)reader["ShipMethodID"];
                        salesOrderHeader.ShipToAddressID = (int)reader["ShipToAddressID"];
                        salesOrderHeader.Status = (byte)reader["Status"];
                        salesOrderHeader.SubTotal = (decimal)reader["SubTotal"];
                        salesOrderHeader.TaxAmt = (decimal)reader["TaxAmt"];
                    }

                    stopWatch.Stop();
                    reader.Close();
                    await PrintTestFindByPKReport(stopWatch.ElapsedMilliseconds, salesOrderHeader.SalesOrderId.ToString());
                }

我使用as关键字来构建可空列,这是正确的吗? 这是Dapper的代码。

using (var conn = new SqlConnection(ConnectionString))
        {
            conn.Open();
            var stopWatch = new Stopwatch();

            for (var i = 0; i < keys.GetLength(0); i++)
            {
                for (var r = 0; r < keys.GetLength(1); r++)
                {
                    stopWatch.Restart();
                    var result = (await conn.QueryAsync<SalesOrderHeader>("SELECT * FROM Sales.SalesOrderHeader WHERE SalesOrderID = @Id", new { Id = keys[i, r] })).FirstOrDefault();
                    stopWatch.Stop();
                    await PrintTestFindByPKReport(stopWatch.ElapsedMilliseconds, result.ToString());
                }
            }
        }
答案

这是一种使您的ADO.NET代码更快的方法。

当您进行选择时,列出您选择的字段而不是使用select *。这样,即使数据库中的顺序发生变化,也可以确保字段返回的顺序。然后从Reader中获取这些字段时,按索引而不是按名称获取。使用和索引更快。

此外,我建议不要将字符串数据库字段置为可空,除非有强烈的商业原因。如果没有值,则只在数据库中存储空字符串。最后,我建议在Get上使用DataReader方法来获取它们所在类型的字段,以便在代码中不需要进行转换。因此,例如,而不是将DataReader[index++]值作为int使用DataReader.GetInt(index++)

例如,这段代码:

 salesOrderHeader = new SalesOrderHeaderSQLserver();
 salesOrderHeader.SalesOrderId = (int)reader["SalesOrderId"];
 salesOrderHeader.SalesOrderNumber =       reader["SalesOrderNumber"] as string;
 salesOrderHeader.AccountNumber = reader["AccountNumber"] as string;

 int index = 0;
 salesOrderHeader = new SalesOrderHeaderSQLserver();
 salesOrderHeader.SalesOrderId = reader.GetInt(index++);
 salesOrderHeader.SalesOrderNumber = reader.GetString(index++);
 salesOrderHeader.AccountNumber = reader.GetString(index++);

给它一个旋转,现在看到它为你做。

另一答案

如果对db或反射有任何疑问,我会问自己,“Marc Gravell会做什么?”。

在这种情况下,他会使用FastMember!你也应该这样做。它是Dapper中数据转换的基础,可以很容易地用于将您自己的DataReader映射到对象(如果您不想使用Dapper)。

下面是一个将SqlDataReader转换为T类型的扩展方法:

请注意:此代码暗示了对FastMember的依赖,并且是为.NET Core编写的(尽管可以很容易地转换为符合.NET Framework / Standard的代码)。

public static T ConvertToObject<T>(this SqlDataReader rd) where T : class, new()
{
    Type type = typeof(T);
    var accessor = TypeAccessor.Create(type);
    var members = accessor.GetMembers();
    var t = new T();

    for (int i = 0; i < rd.FieldCount; i++)
    {
        if (!rd.IsDBNull(i))
        {
            string fieldName = rd.GetName(i);

            if (members.Any(m => string.Equals(m.Name, fieldName, StringComparison.OrdinalIgnoreCase)))
            {
                accessor[t, fieldName] = rd.GetValue(i);
            }
        }
    }

    return t;
}
另一答案

pimbrouwers' answer采取该方法并稍微优化它。减少LINQ调用。

仅映射在对象和数据字段名称中找到的属性。处理DBNull。其他假设是您的域模型属性绝对等于表列/字段名称。

/// <summary>
/// Maps a SqlDataReader record to an object.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dataReader"></param>
/// <param name="newObject"></param>
public static void MapDataToObject<T>(this SqlDataReader dataReader, T newObject)
{
    if (newObject == null) throw new ArgumentNullException(nameof(newObject));

    // Fast Member Usage
    var objectMemberAccessor = TypeAccessor.Create(newObject.GetType());
    var propertiesHashSet =
            objectMemberAccessor
            .GetMembers()
            .Select(mp => mp.Name)
            .ToHashSet();

    for (int i = 0; i < dataReader.FieldCount; i++)
    {
        if (propertiesHashSet.Contains(dataReader.GetName(i)))
        {
            objectMemberAccessor[newObject, dataReader.GetName(i)]
                = dataReader.IsDBNull(i) ? null : dataReader.GetValue(i);
        }
    }
}

样品用法:

public async Task<T> GetAsync<T>(string storedProcedureName, SqlParameter[] sqlParameters = null) where T : class, new()
{
    using (var conn = new SqlConnection(_connString))
    {
        var sqlCommand = await GetSqlCommandAsync(storedProcedureName, conn, sqlParameters);
        var dataReader = await sqlCommand.ExecuteReaderAsync(CommandBehavior.CloseConnection);

        if (dataReader.HasRows)
        {
            var newObject = new T();

            if (await dataReader.ReadAsync())
            { dataReader.MapDataToObject(newObject); }

            return newObject;
        }
        else
        { return null; }
    }
}
另一答案

我拿了pimbrouwers和HouseCat的答案,然后想出了我。在我的场景中,数据库中的列名称具有蛇案例格式。

public static T ConvertToObject<T>(string query) where T : class, new()
    {
        using (var conn = new SqlConnection(AutoConfig.ConnectionString))
        {
            conn.Open();
            var cmd = new SqlCommand(query) {Connection = conn};
            var rd = cmd.ExecuteReader();
            var mappedObject = new T();

            if (!rd.HasRows) return mappedObject;
            var accessor = TypeAccessor.Create(typeof(T));
            var members = accessor.GetMembers();
            if (!rd.Read()) return mappedObject;
            for (var i = 0; i < rd.FieldCount; i++)
            {
                var columnNameFromDataTable = rd.GetName(i);
                var columnValueFromDataTable = rd.GetValue(i);

                var splits = columnNameFromDataTable.Split('_');
                var columnName = new StringBuilder("");
                foreach (var split in splits)
                {
                    columnName.Append(CultureInfo.InvariantCulture.TextInfo.ToTitleCase(split.ToLower()));
                }

                var mappedColumnName = members.FirstOrDefault(x =>
                    string.Equals(x.Name, columnName.ToString(), StringComparison.OrdinalIgnoreCase));

                if(mappedColumnName == null) continue;
                var columnType = mappedColumnName.Type;

                if (columnValueFromDataTable != DBNull.Value)
                {
                    accessor[mappedObject, columnName.ToString()] = Convert.ChangeType(columnValueFromDataTable, columnType);
                }
            }

            return mappedObject;
        }
    }
另一答案

NuGet中有一个SqlDataReader Mapper库,它可以帮助您将SqlDataReader映射到一个对象。以下是它的使用方法(来自GitHub文档):

var mappedObject = new SqlDataReaderMapper<DTOObject>(reader)
    .Build();

或者,如果您想要更高级的映射:

var mappedObject = new SqlDataReaderMapper<DTOObject>(reader)
     .NameTransformers("_", "")
     .ForMember<int>("CurrencyId")
     .ForMember("CurrencyCode", "Code")
     .ForMember<string>("CreatedByUser", "User").Trim()
     .ForMemberManual("CountryCode", val => val.ToString().Substring(0, 10))
     .ForMemberManual("ZipCode", val => val.ToString().Substring(0, 5), "ZIP")
     .Build();

高级映射允许您手动使用名称转换器,更改类型,映射字段,甚至将函数应用于对象的数据,这样即使对象与读者不同,您也可以轻松映射对象。

另一答案

也许我将呈现的方法不是最有效的方法,但只需很少的编码工作即可完成工作。我在这里看到的主要好处是除了构建兼容(可映射)对象之外,您不必处理数据结构。

如果你将SqlDataReader转换为DataTable然后使用JsonConvert.SerializeObject将其序列化,那么你可以使用JsonConvert.DeserializeObject将其反序列化为已知的对象类型

以下是一个实施示例:

        SqlDataReader reader = null;
        SqlConnection myConnection = new SqlConnection();
        myConnection.ConnectionString = ConfigurationManager.ConnectionStrings["DatabaseConnection"].ConnectionString;
        SqlCommand sqlCmd = new SqlCommand();
        sqlCmd.CommandType = CommandType.Text;
        sqlCmd.CommandText = "SELECT * FROM MyTable";
        sqlCmd.Connection = myConnection;
        myConnection.Open();
        reader = sqlCmd.ExecuteReader();

        var dataTable = new DataTable();
        dataTable.Load(reader);

        List<MyObject> myObjects = new List<MyObject>();

        if (dataTable.Rows.Count > 0)
        {
            var serializedMyObjects = JsonConvert.SerializeObject(dataTable);
            // Here you get the object
            myObjects = (List<MyObject>)J

以上是关于将SqlDataReader的结果映射到对象的最快方法的主要内容,如果未能解决你的问题,请参考以下文章

实体框架数据集映射

.NET SqlDataReader对象是否使用数据库游标,或者整个结果集是否已加载到RAM中?

使用表达式目录树实现SqlDataReader到实体的映射

嵌套结构最快的对象重新映射

SqlDataReader 参数不起作用

如何将 spark sql 查询结果映射到对象?