【问题标题】:Dapper with Attributes mapping带有属性映射的 Dapper
【发布时间】:2014-01-06 13:56:59
【问题描述】:

我尝试将我的 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。所以我想知道,这里有没有可能是我想要的,如果是的话,怎么做?

编辑:

我发现了这个话题: Manually Map column names with class properties

通过 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);
            });

【问题讨论】:

  • 好吧,我也尝试了 System.ComponentModel.DataAnnotations 属性中的数据注释,但这些也不起作用。我在想,也许 Dapper 正在使用这些,但我想我错了。那么我的问题是,那我应该使用哪些属性呢?
  • 如果你使用 Dapper,你应该在查询和类之间有一个匹配。所以代替 T1.Id 作为 Table1Id
  • 似乎可以(ab)使用 Linq to SQL 属性。我更新了我的第一篇文章,展示了如何。我在获取泛型类型时遇到了一个问题。
  • 记录在案,关于添加此功能的讨论已有一年之久:github.com/StackExchange/Dapper/issues/722

标签: c# dapper system.reflection


【解决方案1】:

为了完成解决方案,我想把我找到的代码分享给有兴趣的人。

而不是(ab)使用 System.Data.Linq.Mapping.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 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();
        }
    }
}

【讨论】:

  • 我在使用属性时遇到的唯一问题是它会将你的类与 Dapper 联系起来。似乎更好的映射器不需要您使用任何 Dapper 特定属性或元信息来装饰您的 POCO。创建自定义映射的想法是,您不必修改 POCO 以匹配字段名称。
  • @crush - [Column] 属性不是 dapper 的一部分。它被其他 ORM 使用,例如 EntityFramework。它也不是 EntityFramework 的一部分 - 它是 System.ComponentModel.DataAnnotations 的一部分。
  • @Cornelis - 我只是在研究 dapper(我知道有点晚了)并且对映射器有同样的问题,因为我正在使用的数据库中的表/列都是奇怪的名字下划线和大写字母等(arrrh),所以我的映射器将为实体提供 100 条线 - 就像您拥有的反射解决方案一样。谢谢!
  • 我得到 'FallBackTypeMapper' 没有实现接口成员 'SqlMapper.ITypeMap.FindExplicitConstructor()' 如果我实现这个无参数构造函数,当我尝试查询数据库时,我得到一个 NotImplementedException .有什么提示吗?
  • 我在这里找到了解决方案:github.com/henkmollema/Dapper-FluentMap/issues/16
【解决方案2】:

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

FallbackTypeMapper.cs

public ConstructorInfo FindExplicitConstructor()
{
    return _mappers.Select(m => m.FindExplicitConstructor())
        .FirstOrDefault(result => result != null);
}

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

【讨论】:

    【解决方案3】:

    在将 .NET 框架项目迁移到 .NET Core 期间,我遇到了一个与此问题类似的问题。我们在我们的实体上使用了列属性 (System.ComponentModel.DataAnnotations.Schema),它被移到了一个公共库中。我一直在寻找这篇文章中描述的 TypeMap,但我们使用的是 Dapper.FluentMapDapper.FluentMap.Dommel,这是在应用启动中。

    FluentMapper.Initialize(config =>
    {
        ...
        config.ForDommel();
    });
    

    config.ForDommel(); 具有将System.ComponentModel.DataAnnotations.Schema 列属性映射到实体上的中间件,一旦我将其添加到 .NET Core 应用程序,一切正常。希望这会有所帮助,并且它应该比汇总自定义解决方案更容易使用。

    【讨论】:

      【解决方案4】:

      它变得更好

      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))
                          })
              {
              }
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-11-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-04-13
        • 1970-01-01
        • 2013-03-26
        • 2013-06-06
        相关资源
        最近更新 更多