我今天成功启动并运行了一些小型沙箱/POC 项目,但似乎在一个问题上让我头疼......
问题:
有没有办法让 dapper 映射到其中包含空格的 SQL 列名称。
我有一些与此效果相关的内容作为我的结果集。
例如:
SELECT 001 AS [Col 1],
901 AS [Col 2],
00454345345345435349 AS [Col 3],
03453453453454353458 AS [Col 4]
FROM [Some Schema].[Some Table]
我的班级会是这样的
public class ClassA
{
public string Col1 { get; set; }
public string Col2 { get; set; }
///... etc
}
我的实现目前看起来像这样
public Tuple<IList<TClass>, IList<TClass2>> QueryMultiple<TClass, TClass2>(object parameters)
{
List<TClass> output1;
List<TClass2> output2;
using (var data = this.Connection.QueryMultiple(this.GlobalParameter.RpcProcedureName, parameters, CommandType.StoredProcedure))
{
output1 = data.Read<TClass>().ToList();
output2 = data.Read<TClass2>().ToList();
}
var result = new Tuple<IList<TClass>, IList<TClass2>>(output1, output2);
return result;
}
注意:SQL 不能以任何方式修改。
目前我正在检查简洁的代码,我唯一可预见的解决方案是添加一些代码来“说服”列比较,但到目前为止还没有太多运气。
我在 StackOverflow 上看到有类似 dapper 扩展之类的东西,但我希望我可以在不添加扩展的情况下完成此任务,如果没有的话。我会采取最快实施的方式。
有一个 nuget 包 Dapper.FluentMap 允许您添加列名称映射(包括空格)。它类似于 EntityFramework。
// Entity class.
public class Customer
{
public string Name { get; set; }
}
// Mapper class.
public class CustomerMapper : EntityMap<Customer>
{
public CustomerMapper()
{
Map(p => p.Name).ToColumn("Customer Name");
}
}
// Initialise like so -
FluentMapper.Initialize(a => a.AddMap(new CustomerMapper()));
请参阅 https://github.com/henkmollema/Dapper-FluentMap 了解更多信息。
这里的一个选择是通过动态/非通用 API,然后通过每行的
IDictionary<string,object>
API 获取值,但这可能有点乏味。
作为替代方案,您可以创建一个自定义映射器,并告诉 dapper;例如:
SqlMapper.SetTypeMap(typeof(ClassA), new RemoveSpacesMap());
与:
class RemoveSpacesMap : Dapper.SqlMapper.ITypeMap
{
System.Reflection.ConstructorInfo SqlMapper.ITypeMap.FindConstructor(string[] names, Type[] types)
{
return null;
}
SqlMapper.IMemberMap SqlMapper.ITypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo constructor, string columnName)
{
return null;
}
SqlMapper.IMemberMap SqlMapper.ITypeMap.GetMember(string columnName)
{
var prop = typeof(ClassA).GetProperty(columnName.Replace(" ", ""));
return prop == null ? null : new PropertyMemberMap(columnName, prop);
}
class PropertyMemberMap : Dapper.SqlMapper.IMemberMap
{
private string columnName;
private PropertyInfo property;
public PropertyMemberMap(string columnName, PropertyInfo property)
{
this.columnName = columnName;
this.property = property;
}
string SqlMapper.IMemberMap.ColumnName
{
get { throw new NotImplementedException(); }
}
System.Reflection.FieldInfo SqlMapper.IMemberMap.Field
{
get { return null; }
}
Type SqlMapper.IMemberMap.MemberType
{
get { return property.PropertyType; }
}
System.Reflection.ParameterInfo SqlMapper.IMemberMap.Parameter
{
get { return null; }
}
System.Reflection.PropertyInfo SqlMapper.IMemberMap.Property
{
get { return property; }
}
}
}
在尝试通过调用系统 sp_spaceused 过程获取映射结果时,我遇到了类似的问题。 马克的代码不太适合我,因为它抱怨无法找到默认构造函数。 我还使我的版本变得通用,因此理论上可以重复使用。 这可能不是执行速度最快的代码,但它对我有用,并且在我们的情况下,这些调用很少进行。
class TitleCaseMap<T> : SqlMapper.ITypeMap where T: new()
{
ConstructorInfo SqlMapper.ITypeMap.FindConstructor(string[] names, Type[] types)
{
return typeof(T).GetConstructor(Type.EmptyTypes);
}
SqlMapper.IMemberMap SqlMapper.ITypeMap.GetConstructorParameter(ConstructorInfo constructor, string columnName)
{
return null;
}
SqlMapper.IMemberMap SqlMapper.ITypeMap.GetMember(string columnName)
{
string reformattedColumnName = string.Empty;
foreach (string word in columnName.Replace("_", " ").Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
{
reformattedColumnName += char.ToUpper(word[0]) + word.Substring(1).ToLower();
}
var prop = typeof(T).GetProperty(reformattedColumnName);
return prop == null ? null : new PropertyMemberMap(prop);
}
class PropertyMemberMap : SqlMapper.IMemberMap
{
private readonly PropertyInfo _property;
public PropertyMemberMap(PropertyInfo property)
{
_property = property;
}
string SqlMapper.IMemberMap.ColumnName
{
get { throw new NotImplementedException(); }
}
FieldInfo SqlMapper.IMemberMap.Field
{
get { return null; }
}
Type SqlMapper.IMemberMap.MemberType
{
get { return _property.PropertyType; }
}
ParameterInfo SqlMapper.IMemberMap.Parameter
{
get { return null; }
}
PropertyInfo SqlMapper.IMemberMap.Property
{
get { return _property; }
}
}
}
我知道这是一个老问题,但我在上一个项目中遇到了同样的问题,所以我只是使用属性创建了一个自己的映射器。
我定义了一个名为 ColumnNameAttribute.cs 的属性类
using System;
namespace DapperHelper.Attributes
{
[System.AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
sealed class ColumNameAttribute : Attribute
{
private string _columName;
public string ColumnName
{
get { return _columName; }
set { _columName = value; }
}
public ColumNameAttribute(string columnName)
{
_columName = columnName;
}
}
}
定义属性后,我实现了一个动态映射器,它使用 Dapper 中的
Query
方法,但作为 Query<T>
:
using Dapper;
using DapperHelper.Attributes;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Web.Routing;
namespace DapperHelper.Tools
{
public class DynamicMapper<T> :IDisposable where T : class, new()
{
private readonly Dictionary<string, PropertyInfo> _propertiesMap;
public DynamicMapper()
{
_propertiesMap = new Dictionary<string, PropertyInfo>();
PropertyInfo[] propertyInfos = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo propertyInfo in propertyInfos)
{
if (propertyInfo.GetCustomAttribute(typeof(ColumNameAttribute)) is ColumNameAttribute columNameAttribute)
{
_propertiesMap.Add(columNameAttribute.ColumnName, propertyInfo);
}
else
{
_propertiesMap.Add(propertyInfo.Name, propertyInfo);
}
}
}
public List<T> QueryDynamic(IDbConnection dbConnection, string sqlQuery)
{
List<dynamic> results = dbConnection.Query(sqlQuery).ToList();
List<T> output = new List<T>();
foreach (dynamic dynObj in results)
{
output.Add(AssignPropertyValues(dynObj));
}
return output;
}
private T AssignPropertyValues(dynamic dynamicObject)
{
T output = new T();
RouteValueDictionary dynamicObjProps = new RouteValueDictionary(dynamicObject);
foreach (var propName in dynamicObjProps.Keys)
{
if (_propertiesMap.TryGetValue(propName, out PropertyInfo propertyMapped)
&& dynamicObjProps.TryGetValue(propName, out object value))
{
propertyMapped.SetValue(output, value);
}
}
return output;
}
public void Dispose()
{
_propertiesMap.Clear();
}
}
}
要使用它,您必须引用您的 Model 类并定义属性:
using DapperHelper.Attributes;
namespace Testing
{
public class Sample
{
public int SomeColumnData { get; set; }
[ColumnName("Your Column Name")]
public string SpecialColumn{ get; set; }
}
}
然后你可以实现这样的东西:
DynamicMapper<Sample> mapper = new DynamicMapper<Sample>();
List<Sample> samples = mapper.QueryDynamic(connection, "SELECT * FROM Samples");
我希望它可以帮助正在寻找替代方案的人。
创建一个模型类(例如,Employee)
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
现在创建一个 Mapper 类来映射从数据库检索的列(如 SQL)。
例如,如果您要检索 .Net core (Blazor) 中的员工列表,您的代码可能如下所示:
public async Task<IEnumerable<Employee>> GetEmployeeData()
{
return await _db.QueryFromSPAsync<Employee, dynamic>("NameOfYourStoredProcedure",new { });
}
// Employee Mapper class
public class EmployeeMapper : EntityMap<Employee>
{
public EmployeeMapper()
{
Map(p => p.ID).ToColumn("ID");
Map(p => p.FirstName).ToColumn("First Name");
Map(p => p.LastName).ToColumn("Last Name");
}
}
您可以按如下方式初始化Mapper:
//In your Program.cs
FluentMapper.Initialize(Map =>
{
Map.AddMap(new SLAInsuredMapper());
}
);
注意:不要忘记安装
Dapper.FluentMap
NuGet 包。
如果您启用
Dapper.AOT
,现在可以使用属性 UseColumnAttributeAttribute
或属性 DbValueAttribute
。