我正在比较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());
}
}
}
答案 0 :(得分:16)
如果对db或反射有任何疑问,我会问自己,“Marc Gravell会做什么?”。
在这种情况下,他会使用FastMember!你也应该这样做。它是Dapper中数据转换的基础,可以很容易地用于将您自己的DataReader映射到对象(如果您不想使用Dapper)。
以下是将<div class="container">
<div class="jumbotron">
<form method="POST">
<div class="text-left">{% csrf_token %} {{ form.as_p }}
{{ formset.management_form }}
{{ formset }}
</div>
<button type="submit" class="btn btn-primary btn-outline">submit</button>
</form>
</div>
转换为SqlDataReader
类型的扩展方法:
请注意:此代码暗示了对FastMember的依赖,并且是为.NET Core编写的(尽管可以很容易地转换为符合.NET Framework / Standard标准的代码)。
T
答案 1 :(得分:4)
从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; }
}
}
答案 2 :(得分:2)
这是一种使您的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++);
给它一个旋转,看看它现在为你做了。
答案 3 :(得分:1)
我同时回答了小问题和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;
}
}
答案 4 :(得分:1)
List<T> result = new List<T>();
SqlDataReader reader = com.ExecuteReader();
while(reader.Read())
{
Type type = typeof(T);
T obj = (T)Activator.CreateInstance(type);
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
try
{
var value = reader[property.Name];
if (value != null)
property.SetValue(obj, Convert.ChangeType(value.ToString(), property.PropertyType));
}
catch{}
}
result.Add(obj);
}
答案 5 :(得分:0)
NuGet中有一个SqlDataReader映射器库,可帮助您将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();
高级映射允许您手动使用名称转换器,更改类型,映射字段,甚至将功能应用于对象的数据,以便即使对象与阅读器不同也可以轻松映射对象。
答案 6 :(得分:0)
也许我将介绍的方法不是最有效的,但是只需很少的代码即可完成工作。我在这里看到的主要好处是,除了构建兼容(可映射)对象之外,您无需处理数据结构。
如果将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>)JsonConvert.DeserializeObject(serializedMyObjects, typeof(List<MyObject>));
}
myConnection.Close();
答案 7 :(得分:0)
这是基于其他答案的,但是我使用标准反射来读取要实例化的类的属性,并从dataReader中填充它。您还可以使用持久存储的黑白读物存储属性。
初始化一个字典,其中包含来自类型的属性,其名称为键。
var type = typeof(Foo);
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var propertyDictionary = new Dictionary<string,PropertyInfo>();
foreach(var property in properties)
{
if (!property.CanWrite) continue;
propertyDictionary.Add(property.Name, property);
}
从DataReader设置类型的新实例的方法如下:
var foo = new Foo();
//retrieve the propertyDictionary for the type
for (var i = 0; i < dataReader.FieldCount; i++)
{
var n = dataReader.GetName(i);
PropertyInfo prop;
if (!propertyDictionary.TryGetValue(n, out prop)) continue;
var val = dataReader.IsDBNull(i) ? null : dataReader.GetValue(i);
prop.SetValue(foo, val, null);
}
return foo;
如果您想编写一个有效的通用类来处理多种类型,可以将每个字典存储在全局字典中。
答案 8 :(得分:0)
将@HouseCat的solution修改为不区分大小写:
/// <summary>
/// Maps a SqlDataReader record to an object. Ignoring case.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dataReader"></param>
/// <param name="newObject"></param>
/// <remarks>https://stackoverflow.com/a/52918088</remarks>
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(StringComparer.InvariantCultureIgnoreCase);
for (int i = 0; i < dataReader.FieldCount; i++)
{
var name = propertiesHashSet.FirstOrDefault(a => a.Equals(dataReader.GetName(i), StringComparison.InvariantCultureIgnoreCase));
if (!String.IsNullOrEmpty(name))
{
objectMemberAccessor[newObject, name]
= dataReader.IsDBNull(i) ? null : dataReader.GetValue(i);
}
}
}
编辑:这不适用于List<T>
或结果中的多个表。
EDIT2:将调用函数更改为此将适用于列表。无论如何,我将只返回对象列表,如果我期望一个对象,则获取第一个索引。我还没有查看多个表,但是我会的。
public async Task<List<T>> ExecuteReaderAsync<T>(string storedProcedureName, SqlParameter[] sqlParameters = null) where T : class, new()
{
var newListObject = new List<T>();
using (var conn = new SqlConnection(_connectionString))
{
conn.Open();
SqlCommand sqlCommand = GetSqlCommand(conn, storedProcedureName, sqlParameters);
using (var dataReader = await sqlCommand.ExecuteReaderAsync(CommandBehavior.Default))
{
if (dataReader.HasRows)
{
while (await dataReader.ReadAsync())
{
var newObject = new T();
dataReader.MapDataToObject(newObject);
newListObject.Add(newObject);
}
}
}
}
return newListObject;
}
答案 9 :(得分:0)
这有点用
public static object PopulateClass(object o, SQLiteDataReader dr, Type T)
{
Type type = o.GetType();
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
T.GetProperty(property.Name).SetValue(o, dr[property.Name],null);
}
return o;
}
请注意,我在这里使用SQlite,但概念是相同的。作为示例,我通过像这样调用上面的内容来填充Game对象-
g = PopulateClass(g, dr, typeof(Game)) as Game;
请注意,您必须让您的课程与数据阅读器100%匹配,因此请调整查询以适合或传递某种列表以跳过字段。通过将SQLDataReader与SQL Server DB进行通信,您可以在.net和数据库之间实现非常好的类型匹配。使用SQLite,您必须在您的类中将int声明为Int64s才能起作用,并注意向字符串发送空值。但是上述概念似乎行得通,因此应该可以解决。我认为这就是Op的追求。
答案 10 :(得分:0)
我喜欢最受好评的答案提到@MarkGravel和他的FastMember。 但是,如果您已经在使用Dapper,这也是他的组成部分,则可以使用Dapper的GetRowParser
,如下所示:
var parser = reader.GetRowParser<MyObject>(typeof(MyObject));
while (reader.Read())
{
var myObject = parser(reader);
}
答案 11 :(得分:0)
您可以使用命令 DbDataReaderMapper
或使用 IDE 的软件包管理器安装软件包 Install-Package DbDataReaderMapper
。
然后您可以创建您的数据访问对象(我将选择一个比您提供的更短的示例):
class EmployeeDao
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? Age { get; set; }
}
要进行自动映射,您可以调用扩展方法 MapToObject<T>()
var reader = await sqlCmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
var employeeObj = reader.MapToObject<EmployeeDao>();
}
你将摆脱几十行不可读和难以维护的代码。