我们有一个场景,我们使用企业图书馆数据包来满足我们的数据访问需求。我们已经实现了一个Generic Data Reader来将我们的数据融入业务对象。然而,我们正在寻找一种更有效的方法来做到这一点,因为我们正在处理大量数据,这也将在不使用反射的情况下满足对象内子集合的深度复制。通用数据读取器在处理大量数据时也表现不佳。我知道如果我们使用 EF,它可以解决这个问题,但我们还没有达到可以替换整个数据访问层的阶段。是否有替代方案或行业标准来实现这一目标?
你为什么不手工编写映射器的代码?这是最快的方法,而且您在项目生命周期内只创建一次映射器,对吗?在活了五年十年的项目中,与你得到的相比,它所花费的时间可以忽略不计(容易纠正错误,不需要解释 OR/M 错误或它们的“简单性”)。
public interface IEntityMapper
{
object Map(IDataRecord record);
}
public void UserMapper : IEntityMapper
{
public void Map(IDataRecord record)
{
var user = new User();
user.FirstName = record["firstName"]
}
}
如果您有大量记录,则需要使用
number
索引器来防止名称查找:
public void UserMapper : IEntityMapper
{
public void Map(IDataRecord record)
{
var user = new User();
//requires that you specify columns in your SELECT query
//to not break the mapper in future versions.
user.FirstName = record[0]
}
}
在您的存储库中,您可以这样做:
public class UserRepository
{
private readonly IDbConnection _connection;
private UserMapper _mapper = new UserMapper();
public IReadOnlyList<User> GetAllUsers()
{
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = "SELECT Id, UserName FROM Users";
using (var reader = cmd.ExecuteReader())
{
var users = new List<User>();
while (reader.Read())
{
var user = _mapper.Map(reader);
users.Add(user);
}
return users;
}
}
}
}
您甚至可以将所有内容移动到扩展方法:
public static class CommandExtensions
{
public static IReadOnlyCollection<T> ToList<T>(this IDbCommand cmd, IDataMapper mapper)
{
using (var reader = cmd.ExecuteReader())
{
var items = new List<T>();
while (reader.Read())
{
var item= _mapper.Map(reader);
items.Add(item);
}
return item;
}
}
}
为您提供更轻量级的存储库:
public class UserRepository
{
private readonly IDbConnection _connection;
private UserMapper _mapper = new UserMapper();
public IReadOnlyList<User> GetAllUsers()
{
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = "SELECT Id, UserName FROM Users";
return cmd.ToList<User>(_mapper);
}
}
}
深拷贝
关于深拷贝,我尽量避免使用复杂的实体(除非子实体实际上是值对象)。最好使用 ID 引用其他实体,否则很难在代码中的一个地方保持对实体的责任。
说到映射,我可能会引入一个约定,即使用映射接口中的替代方法映射子对象:
public interface IEntityMapper
{
object Map(IDataRecord record);
object Map(IDataRecord record, string prefix);
}
前缀可以用作约定,表示使用连接从多个表中获取信息。
public void UserMapper : IEntityMapper
{
private AddressMapper _childMapper = new AddressMapper();
public void Map(IDataRecord record)
{
var user = new User();
user.FirstName = record["FirstName"]
user.Address = _childMapper.Map(record, "Address_");
}
}
.. 这将使您的存储库方法看起来像这样:
public class UserRepository
{
private readonly IDbConnection _connection;
private UserMapper _mapper = new UserMapper();
public IReadOnlyList<User> GetAllUsers()
{
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = @"SELECT Id, UserName, Address.City as Address_City
FROM Users
JOIN Address ON (Address.Id = Users.AddressId)";
return cmd.ToList<User>(_mapper);
}
}
}
我写过关于 ADO.NET 和(我的)最佳实践的文章:http://blog.gauffin.org/2013/01/04/ado-net-the-right-way/
使用
System.Linq.Expressions.Expression
,可以动态构建接近硬编码效率的动态代码
动手做一个简单的ADO内核:
// Code by Flithor(Mr. Squirrel.Downy)
// License: MIT
// == Warning ==
// Does not work in every possible situation
// Use at your own risk
/// <summary>
/// Getter <see cref="MethodInfo"/> of each common type of <see cref="IDataRecord"/>
/// </summary>
private static readonly Dictionary<Type, MethodInfo> IDataRecordGetterMethods = typeof(IDataRecord).GetMethods()
.Where(m => m.GetParameters().Length == 1 && m.Name == "Get" + m.ReturnType.Name)
.ToDictionary(m => m.ReturnType);
/// <summary>
/// The <see cref="MethodInfo"/> of<see cref="IDataRecord.IsDBNull"/> of <see cref="IDataRecord"/>
/// </summary>
private static readonly MethodInfo isDbNullMethod = typeof(IDataRecord).GetMethod(nameof(IDataRecord.IsDBNull));
/// <summary>
/// Convert <see cref="IDataReader "/> to an entity sequence
/// </summary>
public static IEnumerable<T> CastTo<T>(this IDataReader reader) where T : new()
{
Func<IDataRecord, T> instanceBuilder = null;
while (reader.Read())
{
if (instanceBuilder == null)
{
var sw = Stopwatch.StartNew();
// get orderd fields from first line
var fields = Enumerable.Range(0, reader.FieldCount).Select(reader.GetName).ToArray();
instanceBuilder = BuildAssginer<T>(fields);
// build lambda just spent about 10 ms
Console.WriteLine($"Build lambda cost:{sw.Elapsed}");
}
yield return instanceBuilder(reader);
}
}
/// <summary>
/// Build Entity builder lambda
/// </summary>
/// <param name="fields">Orderd fields of result datas</param>
private static Func<IDataRecord, TResult> BuildAssginer<TResult>(string[] fields) where TResult : new()
{
var createdType = typeof(TResult);
// new Tresult()
var ctor = Expression.New(createdType);
var properties = typeof(TResult).GetProperties().ToDictionary(pinfo => pinfo.Name.ToLower());
//Map indexs of fields on PropertyInfo, for efficiency
var indexToPropMap = fields.Select((field, index) => new { field, index })
.GroupJoin(properties, o => o.field.ToLower(), prop => prop.Key.ToLower(),
(o, props) => new { o.index, prop = props.FirstOrDefault().Value })
.Where(o => o.prop != null)
.OrderBy(o => o.index)
.ToDictionary(o => o.index, o => o.prop);
//define lambda input parameter of IDataRecord type
var recordParamExp = Expression.Parameter(typeof(IDataRecord));
//get property
var propAssginExps = indexToPropMap.Select(kv =>
{
// get field type and the underlying type of nullable type(maybe)
var propType = kv.Value.PropertyType;
var underType = Nullable.GetUnderlyingType(propType);
// build get value expression for most most appropriate case
var getValuesExp = underType != null
? Expression.Convert(
Expression.Call(recordParamExp, IDataRecordGetterMethods[underType],
Expression.Constant(kv.Key)), propType)
: (Expression)Expression.Call(recordParamExp, IDataRecordGetterMethods[propType],
Expression.Constant(kv.Key));
// is not value type or is nullable
var propCanNull = !propType.IsValueType || underType != null;
var getFieldValueExp = propCanNull
// may null field check is dbnull or not
? Expression.Condition(
Expression.Call(recordParamExp, isDbNullMethod, Expression.Constant(kv.Key)),
Expression.Default(propType),
getValuesExp)
// return not nullable value directly
: getValuesExp;
// set field
return Expression.Bind(kv.Value, getFieldValueExp);
});
// entity initializer
// sample:
// new TResult
// {
// No = record.GetInt32(0),
// Name = record.GetString(1),
// Num = record.IsNull(2) ? default(Int64) : record.GetInt64(2)
// }
var memberInit = Expression.MemberInit(ctor, propAssginExps);
return Expression.Lambda<Func<IDataRecord, TResult>>(memberInit, recordParamExp).Compile();
}
知道怎么用
System.Linq.Expressions.Expression
,就可以自己写深拷贝了