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)プロパティと一致しないため、上記のコードは機能しません。このシナリオでは、列名とオブジェクトプロパティを手動でマッピングするために(たとえばperson_id => PersonId
)Dapperでできることはありますか?
これはうまくいきます:
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には、 列属性 を指定できる機能がありません。依存関係をプルしない限り、サポートを追加することに反対しません。
Dapperは、プロパティマッパーへのカスタム列をサポートするようになりました。 ITypeMap インターフェースを介してそうします。 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の実装では、属性が存在し、列名のいずれかと一致する必要があるか、プロパティがマップされないことに注意することが重要です。- 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.DefaultTypeMap.MatchNamesWithUnderscores = true;
以下は、属性を必要としないシンプルなソリューションです。これにより、インフラストラクチャコードを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])));
動的および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;
}
Dapper Tests から取得。現在Dapper 1.42にあります。
// 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の例のようなColumnを使用しました)
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; }
}
これを実現する簡単な方法は、クエリの列でエイリアスを使用することです。データベースの列がPERSON_ID
で、オブジェクトのプロパティがID
の場合、クエリでselect PERSON_ID as Id ...
を実行するだけで、Dapperは期待どおりにそれを選択します。
マッピングによる混乱は、境界線が実際のORMの土地に移動することです。それと戦ってDapperを真のシンプルな(高速の)形式に保つ代わりに、SQLを次のようにわずかに変更します。
var sql = @"select top 1 person_id as PersonId,FirstName,LastName from Person";
データベースへの接続を開く前に、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();
}
.NET 4.5.1以降のチェックアウトを使用している場合 Dapper.FluentColumnMapping はLINQスタイルのマッピングに使用します。モデルからデータベースマッピングを完全に分離できます(注釈は不要です)
これは、他の回答の節約です。クエリ文字列を管理するために考えただけです。
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;
}
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);
});
Kaleb Pedersonのソリューションは私にとってはうまくいきました。 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))
})
{
//
}
}
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)));
私はこれが比較的古いスレッドであることを知っていますが、自分がやったことをそこに投げると思いました。
属性マッピングがグローバルに機能するようにしたかった。プロパティ名(デフォルト)に一致するか、クラスプロパティの列属性に一致します。また、マッピングするクラスごとにこれを設定する必要もありませんでした。そのため、アプリの起動時に呼び出す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);
}
));
};
}
}
ものすごく単純。これを書いたばかりで、どの問題に遭遇するのかまだわかりませんが、うまくいきます。