列名とクラスプロパティを手動でマッピングする


173

Dapper micro ORMは初めてです。これまでのところ、単純なORM関連のものに使用できますが、データベースの列名をクラスプロパティにマップできません。

たとえば、次のデータベーステーブルがあります。

Table Name: Person
person_id  int
first_name varchar(50)
last_name  varchar(50)

そして私はPersonというクラスを持っています:

public class Person 
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

テーブルの列名が、クエリ結果から取得したデータをマップしようとしているクラスのプロパティ名と異なることに注意してください。

var sql = @"select top 1 PersonId,FirstName,LastName from Person";
using (var conn = ConnectionFactory.GetConnection())
{
    var person = conn.Query<Person>(sql).ToList();
    return person;
}

上記のコードは、列名がオブジェクトの(Person)プロパティと一致しないため機能しません。このシナリオでは、Dapperでperson_id => PersonId列名とオブジェクトプロパティを手動でマップ(たとえば)するためにできることはありますか?


回答:


80

これはうまくいきます:

var sql = @"select top 1 person_id PersonId, first_name FirstName, last_name LastName from Person";
using (var conn = ConnectionFactory.GetConnection())
{
    var person = conn.Query<Person>(sql).ToList();
    return person;
}

Dapperには列属性を指定できる機能がありません。依存関係を取得しない限り、サポートを追加することに反対しません。


@Sam Saffronは、テーブルエイリアスを指定する方法があります。私は国という名前のクラスを持っていますが、dbのテーブルには、古い命名規則のために非常に複雑な名前があります。
TheVillageIdiot

64
列属性は、ストアドプロシージャの結果をマッピングするのに便利です。
ロニーオーバーバイ、

2
列の属性は、ドメインとエンティティの具体化に使用しているツール実装の詳細との間の物理的またはセマンティックな結合をより簡単に促進するためにも役立ちます。したがって、これに対するサポートを追加しないでください!!!! :)
デレクグリア2014年

tableattributeのときにcolumnattribeが存在しない理由がわかりません。この例は、挿入、更新、およびSPでどのように機能しますか?私はcolumnattribeを見たいと思います、それは非常に単純であり、現在は機能していないlinq-sqlのようなものを実装する他のソリューションからの移行を非常に簡単にします。
Vman

197

Dapperがカスタム列からプロパティマッパーに対応するようになりました。これは、ITypeMapインターフェイスを介して行われます。A CustomPropertyTypeMapのクラスは、この作業のほとんどを行うことができますDapperのによって提供されます。例えば:

Dapper.SqlMapper.SetTypeMap(
    typeof(TModel),
    new CustomPropertyTypeMap(
        typeof(TModel),
        (type, columnName) =>
            type.GetProperties().FirstOrDefault(prop =>
                prop.GetCustomAttributes(false)
                    .OfType<ColumnAttribute>()
                    .Any(attr => attr.Name == columnName))));

そしてモデル:

public class TModel {
    [Column(Name="my_property")]
    public int MyProperty { get; set; }
}

これは、ことに注意することが重要ですCustomPropertyTypeMapの実装では、列名またはプロパティの属性が存在し、一致1がマップされないことが必要です。DefaultTypeMapのクラスは、標準機能を提供し、この動作を変更するために活用することができます。

public class FallbackTypeMapper : SqlMapper.ITypeMap
{
    private readonly IEnumerable<SqlMapper.ITypeMap> _mappers;

    public FallbackTypeMapper(IEnumerable<SqlMapper.ITypeMap> mappers)
    {
        _mappers = mappers;
    }

    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;
    }
    // implement other interface methods similarly

    // required sometime after version 1.13 of dapper
    public ConstructorInfo FindExplicitConstructor()
    {
        return _mappers
            .Select(mapper => mapper.FindExplicitConstructor())
            .FirstOrDefault(result => result != null);
    }
}

そして、それが整ったら、属性が存在する場合に属性を自動的に使用し、それ以外の場合は標準の動作にフォールバックするカスタムタイプマッパーを簡単に作成できます。

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(attr => attr.Name == columnName)
                           )
                   ),
                new DefaultTypeMap(typeof(T))
            })
    {
    }
}

つまり、属性を使用したマップが必要なタイプを簡単にサポートできるようになりました。

Dapper.SqlMapper.SetTypeMap(
    typeof(MyModel),
    new ColumnAttributeTypeMapper<MyModel>());

ここに完全なソースコードの要点があります


私はこれと同じ問題に取り組んでいます...そしてこれは私が行くべきルートのようです...このコードがどこに呼び出されるのかについて私はかなり混乱しています "Dapper.SqlMapper.SetTypeMap(typeof(MyModel)、新しいColumnAttributeTypeMapper <MyModel>()); " stackoverflow.com/questions/14814972/...
ロハンブフナー

クエリを実行する前に一度呼び出す必要があります。これは、静的コンストラクターで行うことができます。たとえば、一度呼び出すだけで十分です。
Kaleb Pederson 2013

7
これを公式の回答にすることをお勧めします-Dapperのこの機能は非常に便利です。
killthrush 2013

3
@Oliver(stackoverflow.com/a/34856158/364568)によって投稿されたマッピングソリューションが機能し、必要なコードが少なくなります。
リガ

4
私は「簡単に」という言葉がとても楽に振り回されるのが大好きです:P
ジョナサンB.

80

しばらくの間、以下が機能するはずです。

Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;

6
これは「クラスプロパティを使用して列名を手動でマップする」という質問に対する答えではありませんが、私にとっては、手動でマップする必要があるよりはるかに優れています(残念ながらPostgreSQLでは、列名にアンダースコアを使用することをお勧めします)。次のバージョンでMatchNamesWithUnderscoresオプションを削除しないでください!ありがとうございました!!!
victorvartan 2016

5
@victorvartan MatchNamesWithUnderscoresオプションを削除する予定はありません。せいぜい、設定APIをリファクタリングする場合、MatchNamesWithUnderscoresメンバーはそのままにして(それでも理想的には機能し[Obsolete]ます)、新しいAPIをユーザーに示すマーカーを追加します。
マークグラベル

4
@MarcGravell回答の冒頭にある「しばらくの間」という言葉は、明確にするために今後のバージョンで削除するのではないかと心配しました!ASP.NET Core上のNpgsqlと一緒に小さなプロジェクトで使用し始めた素晴らしいマイクロORMであるDapperに感謝します。
victorvartan

2
これは簡単に最良の答えです。私は山や作業の山を見つけましたが、ついにこれに遭遇しました。簡単ですが、広告はありませんが、広告はあまりありません。
teaMonkeyFruit

29

これは、属性を必要としないシンプルなソリューションであり、インフラストラクチャコードをPOCOから除外できます。

マッピングを扱うクラスです。すべての列をマップした場合、ディクショナリは機能しますが、このクラスを使用すると、違いのみを指定できます。さらに、列からフィールドを取得したり、フィールドから列を取得したりできるリバースマップも含まれています。これは、SQLステートメントの生成などを行うときに役立ちます。

public class ColumnMap
{
    private readonly Dictionary<string, string> forward = new Dictionary<string, string>();
    private readonly Dictionary<string, string> reverse = new Dictionary<string, string>();

    public void Add(string t1, string t2)
    {
        forward.Add(t1, t2);
        reverse.Add(t2, t1);
    }

    public string this[string index]
    {
        get
        {
            // Check for a custom column map.
            if (forward.ContainsKey(index))
                return forward[index];
            if (reverse.ContainsKey(index))
                return reverse[index];

            // If no custom mapping exists, return the value passed in.
            return index;
        }
    }
}

ColumnMapオブジェクトを設定し、マッピングを使用するようDapperに指示します。

var columnMap = new ColumnMap();
columnMap.Add("Field1", "Column1");
columnMap.Add("Field2", "Column2");
columnMap.Add("Field3", "Column3");

SqlMapper.SetTypeMap(typeof (MyClass), new CustomPropertyTypeMap(typeof (MyClass), (type, columnName) => type.GetProperty(columnMap[columnName])));

これは、POCOのプロパティが、たとえばストアドプロシージャなど、データベースから返されるものと基本的に一致しない場合に適したソリューションです。
2014

1
私は属性を使用することで得られる簡潔さを少し気に入っていますが、概念的にはこの方法はよりクリーンです。POCOをデータベースの詳細に結び付けないためです。
ブルーノブラント

Dapperを正しく理解している場合、Dapperには特定のInsert()メソッドがありません。Execute()だけです...このマッピングアプローチは挿入に使用できますか?または更新?ありがとう
UuDdLrLrSs 2018

29

動的およびLINQを使用して以下を実行します。

    var sql = @"select top 1 person_id, first_name, last_name from Person";
    using (var conn = ConnectionFactory.GetConnection())
    {
        List<Person> person = conn.Query<dynamic>(sql)
                                  .Select(item => new Person()
                                  {
                                      PersonId = item.person_id,
                                      FirstName = item.first_name,
                                      LastName = item.last_name
                                  }
                                  .ToList();

        return person;
    }

12

これを実現する簡単な方法は、クエリの列でエイリアスを使用することです。データベース列がPERSON_IDオブジェクトのプロパティであるID場合はselect PERSON_ID as Id ...、クエリで実行するだけで、Dapperが期待どおりにそれを取得します。


12

現在Dapper 1.42にあるDapper Testsから取得。

// custom mapping
var map = new CustomPropertyTypeMap(typeof(TypeWithMapping), 
                                    (type, columnName) => type.GetProperties().FirstOrDefault(prop => GetDescriptionFromAttribute(prop) == columnName));
Dapper.SqlMapper.SetTypeMap(typeof(TypeWithMapping), map);

Description属性から名前を取得するヘルパークラス(私は@kalebsの例のような列を個人的に使用しました)

static string GetDescriptionFromAttribute(MemberInfo member)
{
   if (member == null) return null;

   var attrib = (DescriptionAttribute)Attribute.GetCustomAttribute(member, typeof(DescriptionAttribute), false);
   return attrib == null ? null : attrib.Description;
}

クラス

public class TypeWithMapping
{
   [Description("B")]
   public string A { get; set; }

   [Description("A")]
   public string B { get; set; }
}

1
説明が定義されていないプロパティでも機能するようにするために、マップのGetDescriptionFromAttributeto return (attrib?.Description ?? member.Name).ToLower();とreturnを変更し、大文字と小文字を区別しないようにします。.ToLower()columnName
サムホワイト

11

マッピングをいじることは、境界線が実際のORM土地に移動することです。それと戦ってDapperを真のシンプルな(高速)形式に保つ代わりに、SQLを次のように少し変更するだけです。

var sql = @"select top 1 person_id as PersonId,FirstName,LastName from Person";

8

データベースへの接続を開く前に、pocoクラスごとに次のコードを実行します。

// Section
SqlMapper.SetTypeMap(typeof(Section), new CustomPropertyTypeMap(
    typeof(Section), (type, columnName) => type.GetProperties().FirstOrDefault(prop =>
    prop.GetCustomAttributes(false).OfType<ColumnAttribute>().Any(attr => attr.Name == columnName))));

次に、データアノテーションをpocoクラスに次のように追加します。

public class Section
{
    [Column("db_column_name1")] // Side note: if you create aliases, then they would match this.
    public int Id { get; set; }
    [Column("db_column_name2")]
    public string Title { get; set; }
}

その後、あなたはすべて準備が整いました。次のようなクエリを呼び出すだけです。

using (var sqlConnection = new SqlConnection("your_connection_string"))
{
    var sqlStatement = "SELECT " +
                "db_column_name1, " +
                "db_column_name2 " +
                "FROM your_table";

    return sqlConnection.Query<Section>(sqlStatement).AsList();
}

1
すべてのプロパティにColumn属性が必要です。マッパーが利用できない場合にプロパティでマップする方法はありますか?
sandeep.gosavi

5

LINQスタイルのマッピングに.NET 4.5.1以降のチェックアウトDapper.FluentColumnMappingを使用している場合。モデルからデータベースマッピングを完全に分離できます(注釈は必要ありません)。


5
私はDapper.FluentColumnMappingの作成者です。モデルからマッピングを分離することは、主要な設計目標の1つでした。関心事を明確に分離するために、コアデータアクセス(つまり、リポジトリインターフェース、モデルオブジェクトなど)をデータベース固有の具体的な実装から分離したいと思いました。言及してくれてありがとう、それが便利だと思ってよかった!:-)
Alexander

github.com/henkmollema/Dapper-FluentMapも同様です。しかし、サードパーティのパッケージはもう必要ありません。DapperがDapper.SqlMapperを追加しました。興味があれば、詳細について私の回答を参照してください。
Tadej 2017

4

これは、他の回答を後押しする貯金箱です。これは、クエリ文字列を管理するために私が考えたことにすぎません。

Person.cs

public class Person 
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public static string Select() 
    {
        return $"select top 1 person_id {nameof(PersonId)}, first_name {nameof(FirstName)}, last_name {nameof(LastName)}from Person";
    }
}

APIメソッド

using (var conn = ConnectionFactory.GetConnection())
{
    var person = conn.Query<Person>(Person.Select()).ToList();
    return person;
}

1

Dapper 1.12を使用しているすべての人にとって、これを実行するために必要なことは次のとおりです。

  • 新しい列属性クラスを追加します。

      [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property]
    
      public class ColumnAttribute : Attribute
      {
    
        public string Name { get; set; }
    
        public ColumnAttribute(string name)
        {
          this.Name = name;
        }
      }

  • この行を検索:

    map = new DefaultTypeMap(type);

    コメントアウトします。

  • 代わりにこれを書いてください:

            map = new CustomPropertyTypeMap(type, (t, columnName) =>
            {
              PropertyInfo pi = t.GetProperties().FirstOrDefault(prop =>
                                prop.GetCustomAttributes(false)
                                    .OfType<ColumnAttribute>()
                                    .Any(attr => attr.Name == columnName));
    
              return pi != null ? pi : t.GetProperties().FirstOrDefault(prop => prop.Name == columnName);
            });


  • わかりません。列ごとの属性マッピングを可能にするために、ユーザーがDapperを変更することをお勧めしていますか?もしそうなら、Dapperに変更を加えることなく、上記で投稿したコードを使用することが可能です。
    Kaleb Pederson 2013

    1
    ただし、モデルタイプのそれぞれに対してマッピング関数を呼び出す必要がありますよね?? すべてのタイプが各タイプのマッピングを呼び出さなくても属性を使用できるように、私は一般的なソリューションに興味があります。
    Uri Abramson 2013

    2
    @UriAbramsonが言及している理由でDefaultTypeMapを置き換えられるように、戦略パターンを使用してDefaultTypeMapを実装したいと思います。code.google.com/p/dapper-dot-net/issues/detail?id=140を
    Richard Collette

    1

    カレブ・ペダーソンの解決策は私にとってうまくいきました。ColumnAttributeTypeMapperを更新して、カスタム属性(同じドメインオブジェクトに2つの異なるマッピングが必要)を許可し、プロパティを更新して、フィールドを派生する必要があり、タイプが異なる場合にプライベートセッターを許可しました。

    public class ColumnAttributeTypeMapper<T,A> : FallbackTypeMapper where A : ColumnAttribute
    {
        public ColumnAttributeTypeMapper()
            : base(new SqlMapper.ITypeMap[]
                {
                    new CustomPropertyTypeMap(
                       typeof(T),
                       (type, columnName) =>
                           type.GetProperties( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(prop =>
                               prop.GetCustomAttributes(true)
                                   .OfType<A>()
                                   .Any(attr => attr.Name == columnName)
                               )
                       ),
                    new DefaultTypeMap(typeof(T))
                })
        {
            //
        }
    }

    1

    私はこれが比較的古いスレッドであることを知っていますが、私が行ったものを投げると思いました。

    属性マッピングをグローバルに機能させる必要がありました。プロパティ名(デフォルト)と一致するか、クラスプロパティの列属性と一致します。また、マッピングするすべてのクラスに対してこれを設定する必要もありませんでした。そのため、アプリの起動時に呼び出すDapperStartクラスを作成しました。

    public static class DapperStart
    {
        public static void Bootstrap()
        {
            Dapper.SqlMapper.TypeMapProvider = type =>
            {
                return new CustomPropertyTypeMap(typeof(CreateChatRequestResponse),
                    (t, columnName) => t.GetProperties().FirstOrDefault(prop =>
                        {
                            return prop.Name == columnName || prop.GetCustomAttributes(false).OfType<ColumnAttribute>()
                                       .Any(attr => attr.Name == columnName);
                        }
                    ));
            };
        }
    }

    ものすごく単純。これを書いたばかりなので、どのような問題が発生するかはわかりませんが、動作します。


    CreateChatRequestResponseはどのようなものですか?また、スタートアップでそれをどのように呼び出していますか?
    グレンF.

    1
    @GlenF。重要なのは、CreateChatRequestResponseがどのように見えるかは問題ではないということです。任意のPOCOにすることができます。これは起動時に呼び出されます。StartUp.csまたはGlobal.asaxのいずれかで、アプリの起動時に呼び出すことができます。
    マットM

    おそらく私は完全に間違っCreateChatRequestResponseていTますが、これがすべてのEntityオブジェクトをどのように反復するかで置き換えられない限り。私が間違っていたら訂正してください。
    Fwd079

    0

    Kalebが解決しようとしている問題の簡単な解決策は、列属性が存在しない場合にプロパティ名を受け入れることです。

    Dapper.SqlMapper.SetTypeMap(
        typeof(T),
        new Dapper.CustomPropertyTypeMap(
            typeof(T),
            (type, columnName) =>
                type.GetProperties().FirstOrDefault(prop =>
                    prop.GetCustomAttributes(false)
                        .OfType<ColumnAttribute>()
                        .Any(attr => attr.Name == columnName) || prop.Name == columnName)));
    
    弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
    Licensed under cc by-sa 3.0 with attribution required.