Чудной. Отображение в столбец SQL с пробелами в именах столбцов

Сегодня мне удалось запустить что-то в виде небольшого проекта с песочницей /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 не может быть изменен каким-либо образом.

В настоящее время я прохожу сквозной код, и мое единственное предсказуемое решение состоит в том, чтобы добавить некоторый код, чтобы "убедить" сравнение столбцов, но пока мне не повезло.

Я видел в Stackru, что есть такие вещи, как расширения dapper, но я надеюсь, что смогу сделать это без добавления расширения, если нет. Я возьму все, что быстрее всего реализовать.

5 ответов

Решение

Одним из вариантов здесь было бы пройти через динамический / неуниверсальный 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; }
        }
    }
}

Есть пакет 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 для получения дополнительной информации.

У меня была похожая проблема при попытке получить сопоставленные результаты при обращении к системной процедуре 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;
        }
    }
}

После определения атрибута я реализовал динамический преобразователь, который используетQueryметод от Dapper, но работает как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");

Я надеюсь, что это может помочь кому-то, кто ищет альтернативу.

Создайте класс модели (например, сотрудник)

      public class Employee
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Теперь создайте класс Mapper для сопоставления столбцов, полученных из БД (например, SQL).

Например, если вы получаете список сотрудников в ядре .Net (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.

Другие вопросы по тегам