Dapper. 如何将名称中带有空格的SQL列映射到对象属性?

24

今天我设法搭建起一个小的沙盒/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不能以任何方式修改。

目前我正在查看Dapper代码,我的唯一可预见的解决方案是添加一些代码来"说服"列比较,但到目前为止没有太多成功的经验。

我在StackOverflow上看到了像Dapper扩展这样的东西,但我希望能在不添加扩展的情况下完成此操作。如果不行,我会采取最快实现的方法。


我也有同样的需求。我通过在查询中为所有列添加别名来解决了这个问题,但这非常繁琐。下面的解决方案可能有效,但如果Dapper可以添加一个属性/参数来忽略空格,而不是每个人都必须添加自定义映射器,那就太好了。这些都是合法的名称,应该能够轻松地将它们映射起来。 - Ian Lee
这是我目前找到的最佳解决方案:https://dev59.com/Ymox5IYBdhLWcg3w5IVv#34856158 - maracuja-juice
5个回答

15

有一个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获取更多信息。


@David,我初始化了FluentMapper。但它似乎不起作用。我想我漏掉了什么。你能详细说明一下如何让它工作吗?如果我的当前代码是return connection.Query<Customer>("SELECT 'My Name' [Customer Name]").First();,如何使其返回Customer对象? - user007
@user007,你能给我展示一下你的Customer类吗?还有你在哪里进行映射? - David McEleney
1
@DavidMcEleney,我使用了你提供的Customer类。你能详细说明一下你是在哪里以及如何编写的 FluentMapper.Initialize(a => a.AddMap(new CustomerMapper())); 吗?我创建了一个新类,代码如下:public class DapperFluentMapInitializer { public static void Init() { FluentMapper.Initialize(a => a.AddMap(new CustomerMapper())); } } - user007
@user007 大约5个月前,我的记忆有点模糊 - 你正在开发什么类型的应用程序?例如,如果它是一个Web应用程序,请打开Global.asax并将其添加到Application_Start事件中... - David McEleney

13

这里有一种选择,可以通过动态/非泛型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; }
        }
    }
}

1
谢谢!我不得不暂时移除Dapper,因为它是一个小(匆忙的)概念验证所需的。但我确实喜欢这个框架/库,我肯定会再次添加它。 - Rohan Büchner
啊..看起来我应该在RemoveSpacesMap中实现FindConstructor方法,对吧?糟糕!抱歉。 - transistor1
复制您的默认实现就可以解决问题:return typeof(ClassA).GetConstructor(new Type[0]).. 在发布之前应该多探索一下;抱歉。 - transistor1
我正在尝试这个解决方案。它要求我实现 FindExplicitConstructor()。有什么建议吗? - Joe

5

当我尝试从调用系统sp_spaceused过程中获取映射结果时,遇到了类似的问题。Marc的代码对我来说不太适用,因为它抱怨找不到默认构造函数。我还将我的版本通用化,以便理论上可以重复使用。这可能不是最快的执行代码,但它对我很有效,在我们的情况下,这些调用很少发生。

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; }
        }
    }
}

2

我知道这是一个老问题,但在我的最后一个项目中遇到了同样的问题,所以我创建了一个使用属性的自己的映射器。

我定义了一个名为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(); 
        }
    }
}

要使用它,您需要引用您的模型类并定义属性:

 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");

我希望这能帮助到寻找替代方案的人。


非常好的回答!我希望你9年前就发了这个。但这似乎是一个灵活的解决方案。肯定点赞 :) - Rohan Büchner
1
@RohanBüchner 感谢你,伙计。我最近在一个项目中成功实现了它,它为我省了很多麻烦。 - DonMiguelSanchez

0
创建一个模型类(例如,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包。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接