我尝试将我的 Id 字段与列属性映射,但由于某种原因,这似乎不起作用,我不明白为什么。我设置了一个测试项目来演示我正在尝试的内容。
首先,我得到了 2 个实体:
实体表1
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();
}
}
}
和实体表2
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 类型的对象。这有效,但属性 Table1.Id 和 Table1.Table2.Id 仍保持 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。所以我想知道,这是否可能是我想要的,如果是,如何实现?
我发现了这个话题: 手动映射列名称与类属性
通过 Kaleb Pederson 第一篇文章中的代码,可以在需要时使用 FallBackTypeMapper 类和 ColumnAttributeTypeMapper 类的属性。所需要做的就是将所需的类添加到类型映射中:
SqlMapper.SetTypeMap(typeof(Table1), new ColumnAttributeTypeMapper<Table1>());
SqlMapper.SetTypeMap(typeof(Table2), new ColumnAttributeTypeMapper<Table2>());
但是对于许多实体来说,这个列表会变得很长。另外,您需要手动将每个类添加到列表中,我想知道这是否可以通过反射自动完成,更通用。我找到了一个能够获取所有类型的代码片段:
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</*???*/>()));
编辑:
经过更多搜索,我找到了上一个问题的解决方案:
typeList.ToList().ForEach(type =>
{
var mapper = (SqlMapper.ITypeMap)Activator.CreateInstance(
typeof(ColumnAttributeTypeMapper<>)
.MakeGenericType(type));
SqlMapper.SetTypeMap(type, mapper);
});
为了完成解决方案,我想与感兴趣的人分享我找到并整理的代码。
与其(ab)使用 System.Data.Linq.Mapping.ColumnAttribute,不如创建我们自己的逻辑(并且可能节省,尽管 Microsoft 更改 linq to 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 assem in AppDomain.CurrentDomain.GetAssemblies().ToList()
from type in assem.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.Initialize:
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();
}
}
}
Cornelis 的答案是正确的,但我想对此添加更新。从 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 特定版本推出自己的类。
在 .NET Framework 项目迁移到 .NET Core 期间,我遇到了与此问题类似的问题。我们在实体上使用列属性 (
System.ComponentModel.DataAnnotations.Schema
),这些属性已移至公共库。我正在寻找本文中描述的 TypeMap,但是我们使用的是 Dapper.FluentMap
和 Dapper.FluentMap.Dommel
,而且这是在应用程序启动中。
FluentMapper.Initialize(config =>
{
...
config.ForDommel();
});
config.ForDommel();
具有映射实体上 System.ComponentModel.DataAnnotations.Schema
列属性的中间件,将其添加到 .NET Core 应用程序后,一切都正常工作。希望这会有所帮助,并且它应该比汇总自定义解决方案更容易使用。
变得更好了
public class ColumnOrForeignKeyAttributeTypeMapper<T> : FallBackTypeMapper
{
public ColumnOrForeignKeyAttributeTypeMapper()
: base(new SqlMapper.ITypeMap[]
{
new CustomPropertyTypeMap(typeof(T),
(type, columnName) =>
type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.Where(a=>a is ColumnAttribute || a is ForeignKeyAttribute)
.Any(attribute => attribute.GetType() == typeof(ColumnAttribute)
? ((ColumnAttribute)attribute).Name == columnName
: ((ForeignKeyAttribute)attribute).Name == columnName)
)
),
new DefaultTypeMap(typeof(T))
})
}
}
}