簡體   English   中英

具有屬性映射的 Dapper

[英]Dapper with Attributes mapping

我嘗試用列屬性映射我的 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>());

但是對於許多實體,這個列表會變得很長。 此外,您需要手動將每個類添加到列表中,我想知道這是否可以通過 Reflection 自動完成,更通用。 我找到了一個能夠獲取所有類型的代碼片段:

        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,它可能會更多的邏輯(並且可能會保存,盡管微軟將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();
        }
    }
}

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特定版本滾動自己的類。

變得更好

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

在將 .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 應用程序,一切正常。 希望這會有所幫助,它應該比匯總自定義解決方案更易於使用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM