Dapper с отображением атрибутов
Я пытаюсь сопоставить свои поля Id с атрибутами столбцов, но по какой-то причине это не работает, и я не могу понять, почему. Я создал тестовый проект, чтобы продемонстрировать, что я пытаюсь.
во-первых, я получил свои 2 сущности:
Сущность Table1
using System.Data.Linq.Mapping;
namespace DapperTestProj
{
public class Table1
{
[Column(Name = "Table1Id")]
public int Id { get; set; }
public string Column1 { get; set; }
public string Column2 { get; set; }
public Table2 Table2 { get; set; }
public Table1()
{
Table2 = new Table2();
}
}
}
и сущность Table2
using System.Data.Linq.Mapping;
namespace DapperTestProj
{
public class Table2
{
[Column(Name = "Table2Id")]
public int Id { get; set; }
public string Column3 { get; set; }
public string Column4 { get; set; }
}
}
в моей базе данных я получил 2 таблицы, также названные Table1 и Table2. Обе таблицы получили свои столбцы с именем, равным сущностям, за исключением Table1 столбец с именем Table2Id, а также внешний ключ между Table1.Table2Id и Table2.Id.
также есть 1 запись в обеих таблицах, и те получили оба Id 2.
далее я пытаюсь выполнить запрос с помощью dapper, и он должен вернуть объект типа Table1. Это работает, но как собственность Таблица1.ID и Таблица1.Таблица2.Идентификатор остается 0 (целое число, по умолчанию). Я ожидаю, что атрибуты столбцов будут сопоставлять поля Id, но, очевидно, это не так Харуки.
это запрос и отображение, которые я выполняю в коде:
private Table1 TestMethod(IDbConnection connection)
{
var result = connection.Query<Table1, Table2, Table1>(
@"SELECT
T1.Id as Table1Id,
T1.Column1 as Column1,
T1.Column2 as Column2,
T2.Id as Table2Id,
T2.Column3 as Column3,
T2.Column4 as Column4
FROM Table1 T1
INNER JOIN Table2 T2 ON T1.Table2Id = T2.Id",
(table1, table2) =>
{
table1.Table2 = table2;
return table1;
},
splitOn: "Table2Id"
).SingleOrDefault();
return result;
}
теперь я мог бы переименовать оба поля свойств Id в сущностях в Table1Id и Table2Id, но я предпочитаю Id вместо причины более логического кода, такого как Table1.Id вместо Table1.Table1Id. Поэтому мне было интересно, возможно ли то, что я хочу здесь, и если да, то как?
Edit:
Я нашел эту тему: вручную сопоставить имена столбцов с класса свойства
и с кодом в первом посте Калеба Педерсона можно использовать атрибуты, когда это необходимо с классом FallBackTypeMapper и классом ColumnAttributeTypeMapper. Все, что нужно, это добавить необходимые классы в typemapping с помощью:
SqlMapper.SetTypeMap(typeof(Table1), new ColumnAttributeTypeMapper<Table1>());
SqlMapper.SetTypeMap(typeof(Table2), new ColumnAttributeTypeMapper<Table2>());
но со многими структурами этот список будет расти долго. Также вам нужно добавить каждый класс вручную в список, и мне было интересно, можно ли это сделать автоматически en более общим с Отображение. Я нашел фрагмент кода, который способен получить все типы:
const string @namespace = "DapperTestProj.Entities";
var types = from type in Assembly.GetExecutingAssembly().GetTypes()
where type.IsClass && type.Namespace == @namespace
select type;
и петля через все типы, я могу это сделать, только проблема у меня сейчас в том, какой фрагмент кода мне нужно иметь или нужно поставить на место, где вопросительные знаки прямо сейчас?
typeList.ToList().ForEach(type => SqlMapper.SetTypeMap(type,
new ColumnAttributeTypeMapper</*???*/>()));
Edit:
после поисков, я нашел решение для моей проблемы:
typeList.ToList().ForEach(type =>
{
var mapper = (SqlMapper.ITypeMap)Activator.CreateInstance(
typeof(ColumnAttributeTypeMapper<>)
.MakeGenericType(type));
SqlMapper.SetTypeMap(type, mapper);
});
2 ответов
для завершения решения я хочу поделиться кодом, который я нашел и собрал вместе с теми, кто заинтересован.
вместо (ab) с помощью системы.Данные.В LINQ.Отображение.ColumnAttribute, это может быть более логичным (и, вероятно, сохранить, хотя вероятность того, что Microsoft изменит класс linq на sql ColumnAttribute), чтобы создать наш собственный класс ColumnAttribute:
ColumnAttribute.cs
using System;
namespace DapperTestProj.DapperAttributeMapper //Maybe a better namespace here
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class ColumnAttribute : Attribute
{
public string Name { get; set; }
public ColumnAttribute(string name)
{
Name = name;
}
}
}
найти в тема, которую я упоминал ранее, классы FallBackTypeMapper и ColumnAttributeTypeMapper:
FallBackTypeMapper.cs
using System;
using System.Collections.Generic;
using System.Reflection;
using Dapper;
namespace DapperTestProj.DapperAttributeMapper
{
public class FallBackTypeMapper : SqlMapper.ITypeMap
{
private readonly IEnumerable<SqlMapper.ITypeMap> _mappers;
public FallBackTypeMapper(IEnumerable<SqlMapper.ITypeMap> mappers)
{
_mappers = mappers;
}
public ConstructorInfo FindConstructor(string[] names, Type[] types)
{
foreach (var mapper in _mappers)
{
try
{
var result = mapper.FindConstructor(names, types);
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;
}
public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
{
foreach (var mapper in _mappers)
{
try
{
var result = mapper.GetConstructorParameter(constructor, 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;
}
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;
}
}
}
ColumnAttributeTypeMapper.cs
using System.Linq;
using Dapper;
namespace DapperTestProj.DapperAttributeMapper
{
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(attribute => attribute.Name == columnName)
)
),
new DefaultTypeMap(typeof(T))
})
{
}
}
}
и наконец,TypeMapper.cs для инициализации карт.
using System;
using System.Linq;
using System.Reflection;
using Dapper;
namespace DapperTestProj.DapperAttributeMapper
{
public static class TypeMapper
{
public static void Initialize(string @namespace)
{
var types = from type in Assembly.GetExecutingAssembly().GetTypes()
where type.IsClass && type.Namespace == @namespace
select type;
types.ToList().ForEach(type =>
{
var mapper = (SqlMapper.ITypeMap)Activator
.CreateInstance(typeof(ColumnAttributeTypeMapper<>)
.MakeGenericType(type));
SqlMapper.SetTypeMap(type, mapper);
});
}
}
}
при запуске, TypeMapper.Инициализировать нужно вызвать:
TypeMapper.Initialize("DapperTestProj.Entities");
и вы можете начать использовать атрибуты для сущностей свойства
using DapperTestProj.DapperAttributeMapper;
namespace DapperTestProj.Entities
{
public class Table1
{
[Column("Table1Id")]
public int Id { get; set; }
public string Column1 { get; set; }
public string Column2 { get; set; }
public Table2 Table2 { get; set; }
public Table1()
{
Table2 = new Table2();
}
}
}
ответ Корнелиса правильный, однако я хотел добавить обновление к этому. Начиная с текущей версии Dapper вам также необходимо реализовать SqlMapper.ItypeMap.FindExplicitConstructor()
. Я не уверен, когда это изменение было сделано, но это для всех, кто натыкается на этот вопрос и упускает эту часть решения.
внутри FallbackTypeMapper.cs
public ConstructorInfo FindExplicitConstructor()
{
return _mappers.Select(m => m.FindExplicitConstructor())
.FirstOrDefault(result => result != null);
}
также вы можете использовать ColumnAttribute
класса, расположенный в System.ComponentModel.DataAnnotations.Schema
пространство имен вместо прокатки свой собственный встроенная версия без базы данных/orm.