web-dev-qa-db-ja.com

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();
        }
    }
}

およびエンティティ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; }
    }
}

私のデータベースでは、Table1とTable2という名前の2つのテーブルを取得しました。どちらのテーブルも、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に変更できましたが、Table1.Table1IdではなくTable1.Idのようなより多くのロジックコードの代わりにIdを優先します。だから私は疑問に思っていました、私がここで欲しいものは可能ですか?

編集:

私はこのトピックを見つけました: クラスのプロパティを使用して手動で列名をマップします

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);
            });
15
Cornelis

ソリューションを完成させるために、見つけたコードを興味のある人と共有したいと思います。

System.Data.Linq.Mapping.ColumnAttributeを(ab)使用する代わりに、独自のColumnAttributeを作成するためのロジックが増える可能性があります(おそらく保存されますが、Microsoftがlinqをsql 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.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();
        }
    }
}
19
Cornelis

Cornelisの答えは正しいですが、これに更新を追加したいと思いました。 Dapperの現在のバージョンでは、SqlMapper.ItypeMap.FindExplicitConstructor()も実装する必要があります。いつこの変更が行われたのかはわかりませんが、この質問につまずいて、解決策のその部分が欠けている他の人にとっては、これが当てはまります。

FallbackTypeMapper.cs

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

また、組み込みの非データベース/ orm固有のバージョンに独自にロールする代わりに、System.ComponentModel.DataAnnotations.Schema名前空間内にあるColumnAttributeクラスを使用することもできます。

2
JNYRanger

それはさらに良くなる

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