Dapper with Attributes mapping

Cor*_*lis 14 c# system.reflection dapper

我尝试使用列属性映射我的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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

和实体表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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的数据库中,我得到了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;
}
Run Code Online (Sandbox Code Playgroud)

现在我可以将实体中的两个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>());
Run Code Online (Sandbox Code Playgroud)

但是对于许多实体来说,这个列表会变长.此外,您需要手动将每个类添加到列表中,我想知道是否可以使用Reflection自动完成此更通用的操作.我找到了一个能够获得所有类型的代码片段:

        const string @namespace = "DapperTestProj.Entities";

        var types = from type in Assembly.GetExecutingAssembly().GetTypes()
                    where type.IsClass && type.Namespace == @namespace
                    select type;
Run Code Online (Sandbox Code Playgroud)

循环遍历所有类型,我可以做到这一点,我现在唯一的问题是我需要具有什么代码片段或者需要放在问号现在的地方?

        typeList.ToList().ForEach(type => SqlMapper.SetTypeMap(type, 
                               new ColumnAttributeTypeMapper</*???*/>()));
Run Code Online (Sandbox Code Playgroud)

编辑:

经过更多搜索,我找到了解决我最后一个问题的方法:

        typeList.ToList().ForEach(type =>
            {
                var mapper = (SqlMapper.ITypeMap)Activator.CreateInstance(
                    typeof(ColumnAttributeTypeMapper<>)
                        .MakeGenericType(type));
                SqlMapper.SetTypeMap(type, mapper);
            });
Run Code Online (Sandbox Code Playgroud)

Cor*_*lis 20

为了完成解决方案,我想分享我找到的代码并与感兴趣的人一起整理.

而不是(ab)使用System.Data.Linq.Mapping.ColumnAttribute,它可能更逻辑(并且可能保存,尽管微软会将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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在前面提到的主题中找到了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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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)) 
                    })
        {
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,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);
            });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在启动时,需要调用TypeMapper.Initialize:

TypeMapper.Initialize("DapperTestProj.Entities");
Run Code Online (Sandbox Code Playgroud)

您可以开始使用实体属性的属性

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我使用属性时遇到的唯一问题是它将你的类与 Dapper 联系起来。似乎更好的映射器不会要求您使用任何 Dapper 特定属性或元信息来装饰 POCO。创建自定义映射的想法是这样您就不必修改 POCO 来匹配字段名称。 (2认同)
  • @crush - `[Column]`属性不是dapper的一部分.它被其他ORM使用,例如EntityFramework.它也不是EntityFramework的一部分 - 它是System.ComponentModel.DataAnnotations的一部分. (2认同)

JNY*_*ger 7

Cornelis 的回答是正确的,但是我想对此添加更新。从 Dapper 的当前版本开始,您还需要实现SqlMapper.ItypeMap.FindExplicitConstructor(). 我不确定何时进行此更改,但这对于偶然发现此问题并缺少该部分解决方案的任何其他人。

FallbackTypeMapper.cs 中

public ConstructorInfo FindExplicitConstructor()
{
    return _mappers.Select(m => m.FindExplicitConstructor())
        .FirstOrDefault(result => result != null);
}
Run Code Online (Sandbox Code Playgroud)

您也可以使用ColumnAttribute位于System.ComponentModel.DataAnnotations.Schema命名空间内的类,而不是为内置的非数据库/orm 特定版本滚动您自己的类。