Вручную сопоставить имена столбцов со свойствами класса
Я новичок в Dapper Micro ORM. До сих пор я могу использовать его для простых материалов, связанных с ORM, но я не могу сопоставить имена столбцов базы данных со свойствами класса. Например:
у меня есть таблица базы данных выглядит следующим образом:
Table Name: Person
person_id int
first_name varchar(50)
last_name varchar(50)
и у меня есть класс под названием Person
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
обратите внимание, что мои имена столбцов в таблице отличаются от имени свойства класса, к которому я пытаюсь сопоставить данные, полученные из запроса результат.
var sql = @"select top 1 PersonId,FirstName,LastName from Person";
using (var conn = ConnectionFactory.GetConnection())
{
var person = conn.Query<Person>(sql).ToList();
return person;
}
приведенный выше код не будет работать, поскольку имена столбцов не будут совпадать со свойствами объекта (человека). В этом случае есть ли что-нибудь, что я могу сделать в Dapper, чтобы вручную сопоставить (например,person_id => PersonId) имена столбцов со свойствами объекта?
любая подсказка или помощь будут высоко оценены.
13 ответов:
Это прекрасно работает:
var sql = @"select top 1 person_id PersonId, first_name FirstName, last_name LastName from Person"; using (var conn = ConnectionFactory.GetConnection()) { var person = conn.Query<Person>(sql).ToList(); return person; }Dapper не имеет объекта, который позволяет указать Атрибут Столбца, Я не против добавления поддержки для него, при условии, что мы не тянем в зависимости.
Dapper теперь поддерживает пользовательские столбцы для отображения свойств. Он делает это через ITypeMap интерфейс. А CustomPropertyTypeMap класс предоставляется Dapper, который может выполнять большую часть этой работы. Например:
Dapper.SqlMapper.SetTypeMap( typeof(TModel), new CustomPropertyTypeMap( typeof(TModel), (type, columnName) => type.GetProperties().FirstOrDefault(prop => prop.GetCustomAttributes(false) .OfType<ColumnAttribute>() .Any(attr => attr.Name == columnName))));и модели:
public class TModel { [Column(Name="my_property")] public int MyProperty { get; set; } }важно отметить, что реализация CustomPropertyTypeMap требует, чтобы атрибут существовал и соответствовал одному из имен столбцов, или свойство не будет сопоставлено. Элемент DefaultTypeMap класс предоставляет стандартную функциональность и может быть использован для изменения этого поведения:
public class FallbackTypeMapper : SqlMapper.ITypeMap { private readonly IEnumerable<SqlMapper.ITypeMap> _mappers; public FallbackTypeMapper(IEnumerable<SqlMapper.ITypeMap> mappers) { _mappers = mappers; } public SqlMapper.IMemberMap GetMember(string columnName) { foreach (var mapper in _mappers) { try { var result = mapper.GetMember(columnName); if (result != null) { return result; } } catch (NotImplementedException nix) { // the CustomPropertyTypeMap only supports a no-args // constructor and throws a not implemented exception. // to work around that, catch and ignore. } } return null; } // implement other interface methods similarly // required sometime after version 1.13 of dapper public ConstructorInfo FindExplicitConstructor() { return _mappers .Select(mapper => mapper.FindExplicitConstructor()) .FirstOrDefault(result => result != null); } }и с этим на месте становится легко создать пользовательский тип mapper, который будет автоматически использовать атрибуты, если они присутствуют, но в противном случае вернется к стандартному поведению:
public class ColumnAttributeTypeMapper<T> : FallbackTypeMapper { public ColumnAttributeTypeMapper() : base(new SqlMapper.ITypeMap[] { new CustomPropertyTypeMap( typeof(T), (type, columnName) => type.GetProperties().FirstOrDefault(prop => prop.GetCustomAttributes(false) .OfType<ColumnAttribute>() .Any(attr => attr.Name == columnName) ) ), new DefaultTypeMap(typeof(T)) }) { } }это означает, что теперь мы можем легко поддерживать типы, которые требуют map с помощью атрибутов:
Dapper.SqlMapper.SetTypeMap( typeof(MyModel), new ColumnAttributeTypeMapper<MyModel>());здесь Gist to полный исходный код.
в течение некоторого времени, следующее должно работать:
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
вот простое решение, которое не требует атрибутов, позволяющих сохранить код инфраструктуры из ваших POCOs.
это класс для работы с отображениями. Словарь будет работать, если вы сопоставили все столбцы, но этот класс позволяет указать только различия. Кроме того, он включает в себя обратные Карты, чтобы вы могли получить поле из столбца и столбец из поля, что может быть полезно при выполнении таких вещей, как создание инструкций sql.
public class ColumnMap { private readonly Dictionary<string, string> forward = new Dictionary<string, string>(); private readonly Dictionary<string, string> reverse = new Dictionary<string, string>(); public void Add(string t1, string t2) { forward.Add(t1, t2); reverse.Add(t2, t1); } public string this[string index] { get { // Check for a custom column map. if (forward.ContainsKey(index)) return forward[index]; if (reverse.ContainsKey(index)) return reverse[index]; // If no custom mapping exists, return the value passed in. return index; } } }установите объект ColumnMap и скажите Dapper использовать отображение.
var columnMap = new ColumnMap(); columnMap.Add("Field1", "Column1"); columnMap.Add("Field2", "Column2"); columnMap.Add("Field3", "Column3"); SqlMapper.SetTypeMap(typeof (MyClass), new CustomPropertyTypeMap(typeof (MyClass), (type, columnName) => type.GetProperty(columnMap[columnName])));
Я делаю следующее С помощью dynamic и LINQ:
var sql = @"select top 1 person_id, first_name, last_name from Person"; using (var conn = ConnectionFactory.GetConnection()) { List<Person> person = conn.Query<dynamic>(sql) .Select(item => new Person() { PersonId = item.person_id, FirstName = item.first_name, LastName = item.last_name } .ToList(); return person; }
простой способ добиться этого-просто использовать псевдонимы в Столбцах вашего запроса. Если ваш столбец базы данных
PERSON_IDи proprty вашего объектаIDвы можете просто сделатьselect PERSON_ID as Id ...в вашем запросе и Dapper заберет его, как и ожидалось.
возиться с отображением-это пограничное перемещение в реальную землю ORM. Вместо того, чтобы бороться с ним и держать Dapper в его истинной простой (быстрой) форме, просто измените свой SQL немного так:
var sql = @"select top 1 person_id as PersonId,FirstName,LastName from Person";
взято с Тесты Щеголеватый который в настоящее время находится на Dapper 1.42.
// custom mapping var map = new CustomPropertyTypeMap(typeof(TypeWithMapping), (type, columnName) => type.GetProperties().FirstOrDefault(prop => GetDescriptionFromAttribute(prop) == columnName)); Dapper.SqlMapper.SetTypeMap(typeof(TypeWithMapping), map);вспомогательный класс, чтобы получить имя от атрибута описания (я лично использовал столбец, например @kalebs)
static string GetDescriptionFromAttribute(MemberInfo member) { if (member == null) return null; var attrib = (DescriptionAttribute)Attribute.GetCustomAttribute(member, typeof(DescriptionAttribute), false); return attrib == null ? null : attrib.Description; }класс
public class TypeWithMapping { [Description("B")] public string A { get; set; } [Description("A")] public string B { get; set; } }
прежде чем открыть соединение с базой данных, выполните этот фрагмент кода для каждого из ваших классов poco:
// Section SqlMapper.SetTypeMap(typeof(Section), new CustomPropertyTypeMap( typeof(Section), (type, columnName) => type.GetProperties().FirstOrDefault(prop => prop.GetCustomAttributes(false).OfType<ColumnAttribute>().Any(attr => attr.Name == columnName))));затем добавьте аннотации данных в свои классы poco следующим образом:
public class Section { [Column("db_column_name1")] // Side note: if you create aliases, then they would match this. public int Id { get; set; } [Column("db_column_name2")] public string Title { get; set; } }после этого все готово. Просто сделайте запрос вызова, что-то вроде:
using (var sqlConnection = new SqlConnection("your_connection_string")) { var sqlStatement = "SELECT " + "db_column_name1, " + "db_column_name2 " + "FROM your_table"; return sqlConnection.Query<Section>(sqlStatement).AsList(); }
Если вы используете .NET 4.5.1 или выше checkout щеголь.FluentColumnMapping для отображения стиля LINQ. Это позволяет полностью отделить отображение БД от вашей модели (нет необходимости в аннотациях)
это поросенок отступает от других ответов. Это просто мысль, которую я имел для управления строками запроса.
человек.cs
public class Person { public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public static string Select() { return $"select top 1 person_id {nameof(PersonId)}, first_name {nameof(FirstName)}, last_name {nameof(LastName)}from Person"; } }метод API
using (var conn = ConnectionFactory.GetConnection()) { var person = conn.Query<Person>(Person.Select()).ToList(); return person; }
для всех вас, кто использует Dapper 1.12, вот что вам нужно сделать, чтобы сделать это:
Добавить новый класс атрибутов столбцов: [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property] public class ColumnAttribute : Attribute { public string Name { get; set; } public ColumnAttribute(string name) { this.Name = name; } }поиск этой строки: map = new DefaultTypeMap(type);и закомментировать.
вместо того, чтобы написать это : map = new CustomPropertyTypeMap(type, (t, columnName) => { PropertyInfo pi = t.GetProperties().FirstOrDefault(prop => prop.GetCustomAttributes(false) .OfType<ColumnAttribute>() .Any(attr => attr.Name == columnName)); return pi != null ? pi : t.GetProperties().FirstOrDefault(prop => prop.Name == columnName); });
решение Калеба Педерсона сработало для меня. Я обновил ColumnAttributeTypeMapper, чтобы разрешить пользовательский атрибут (имел требование для двух разных сопоставлений на одном объекте домена) и обновил свойства, чтобы разрешить частные установщики в случаях, когда поле должно быть получено, и типы различались.
public class ColumnAttributeTypeMapper<T,A> : FallbackTypeMapper where A : ColumnAttribute { public ColumnAttributeTypeMapper() : base(new SqlMapper.ITypeMap[] { new CustomPropertyTypeMap( typeof(T), (type, columnName) => type.GetProperties( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(prop => prop.GetCustomAttributes(true) .OfType<A>() .Any(attr => attr.Name == columnName) ) ), new DefaultTypeMap(typeof(T)) }) { // } }
Comments