Dapper.NET
マルチマッピング
サーチ…
構文
-
public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>( this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
-
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
-
public static IEnumerable<TReturn> Query<TReturn>(this IDbConnection cnn, string sql, Type[] types, Func<object[], TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
パラメーター
パラメータ | 詳細 |
---|---|
cnn | あなたのデータベース接続は既にオープンしている必要があります。 |
SQL | 実行するコマンド。 |
タイプ | レコードセット内の型の配列。 |
地図 | 返された結果の構築を処理するFunc<> |
パラメータ | パラメータを抽出するオブジェクト。 |
トランザクション | このクエリがある場合はそのトランザクションの一部であるトランザクション。 |
緩衝 | 問合せの結果をバッファリングするかどうか。これは省略可能なパラメータであり、デフォルトはtrueです。 bufferedがtrueの場合、結果はList<T> バッファされ、複数の列挙で安全なIEnumerable<T> として返されます。 bufferedがfalseの場合、sql接続は読み込みが終了するまで開かれたままになり、メモリー内の一度の行を処理できます。複数の列挙により、データベースへの追加の接続が生成されます。バッファリングされた偽のメモリ使用量を削減するための非常に効率的ですが、あなたはレコードだけの非常に小さな断片を維持するならば、それは持って返さかなりのパフォーマンス・オーバーヘッドを熱心に結果セットを実体化に比べて。最後に、多数のバッファリングされていないSQL接続が並行している場合は、接続プールが不要になると、接続が使用可能になるまでブロック要求が発生することを考慮する必要があります。 |
splitOn | フィールドは、2番目のオブジェクトを分割して読み込む必要があります(デフォルト:id)。レコードに複数の型が含まれている場合は、コンマで区切られたリストにすることができます。 |
commandTimeout | コマンド実行タイムアウトまでの秒数。 |
commandType | それはストアドプロシージャかバッチですか? |
シンプルなマルチテーブルマッピング
Personクラスを生成する必要がある残りの騎手のクエリがあるとします。
名 | うまれた | レジデンス |
---|---|---|
ダニエルデネット | 1942 | アメリカ合衆国 |
サムハリス | 1967 | アメリカ合衆国 |
リチャード・ドーキンス | 1941 | イギリス |
public class Person
{
public string Name { get; set; }
public int Born { get; set; }
public Country Residience { get; set; }
}
public class Country
{
public string Residence { get; set; }
}
返されたインスタンスを作成するために使用できるFunc<>
をとるオーバーロードQuery<>
を使用して、 Func<>
クラスとResidenceプロパティをCountryのインスタンスにFunc<>
ことができます。 Func<>
は最大7つの入力タイプをとり、最終総称引数は常に戻り値の型です。
var sql = @"SELECT 'Daniel Dennett' AS Name, 1942 AS Born, 'United States of America' AS Residence
UNION ALL SELECT 'Sam Harris' AS Name, 1967 AS Born, 'United States of America' AS Residence
UNION ALL SELECT 'Richard Dawkins' AS Name, 1941 AS Born, 'United Kingdom' AS Residence";
var result = connection.Query<Person, Country, Person>(sql, (person, country) => {
if(country == null)
{
country = new Country { Residence = "" };
}
person.Residience = country;
return person;
},
splitOn: "Residence");
splitOn: "Residence"
引数の使用に注意してください。この引数は、次のクラスタイプ(この場合はCountry
)の第1列です。 Dapperは自動的に分割するIdという列を探しますが、見つからずsplitOn
が指定されていない場合は、有用なメッセージが表示されてSystem.ArgumentException
がスローされます。したがって、オプションですが、通常はsplitOn
値を指定するsplitOn
ます。
1対多マッピング
1対多の関係を含むより複雑な例を見てみましょう。クエリに重複データが含まれる複数の行が含まれるようになりました。これを処理する必要があります。これはクロージャーで検索します。
サンプルクラスと同様にクエリがわずかに変更されます。
イド | 名 | うまれた | CountryId | 国名 | BookId | BookName |
---|---|---|---|---|---|---|
1 | ダニエルデネット | 1942 | 1 | アメリカ合衆国 | 1 | ブレインストーム |
1 | ダニエルデネット | 1942 | 1 | アメリカ合衆国 | 2 | エルボールーム |
2 | サムハリス | 1967 | 1 | アメリカ合衆国 | 3 | モラルの風景 |
2 | サムハリス | 1967 | 1 | アメリカ合衆国 | 4 | 目を覚ます:宗教のない霊性の指針 |
3 | リチャード・ドーキンス | 1941 | 2 | イギリス | 5 | 現実の魔法:どのように私たちが知っているか本当に真実 |
3 | リチャード・ドーキンス | 1941 | 2 | イギリス | 6 | 不思議のための食欲:科学者の製作 |
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Born { get; set; }
public Country Residience { get; set; }
public ICollection<Book> Books { get; set; }
}
public class Country
{
public int CountryId { get; set; }
public string CountryName { get; set; }
}
public class Book
{
public int BookId { get; set; }
public string BookName { get; set; }
}
remainingHorsemen
預言者には、完全にマテリアライズされた人物オブジェクトのインスタンスが取り込まれます。クエリ結果の各行に対して、lambda引数で定義された型のインスタンスのマップされた値が渡され、これをどのように処理するかはあなた次第です。
var sql = @"SELECT 1 AS Id, 'Daniel Dennett' AS Name, 1942 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 1 AS BookId, 'Brainstorms' AS BookName
UNION ALL SELECT 1 AS Id, 'Daniel Dennett' AS Name, 1942 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 2 AS BookId, 'Elbow Room' AS BookName
UNION ALL SELECT 2 AS Id, 'Sam Harris' AS Name, 1967 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 3 AS BookId, 'The Moral Landscape' AS BookName
UNION ALL SELECT 2 AS Id, 'Sam Harris' AS Name, 1967 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 4 AS BookId, 'Waking Up: A Guide to Spirituality Without Religion' AS BookName
UNION ALL SELECT 3 AS Id, 'Richard Dawkins' AS Name, 1941 AS Born, 2 AS CountryId, 'United Kingdom' AS CountryName, 5 AS BookId, 'The Magic of Reality: How We Know What`s Really True' AS BookName
UNION ALL SELECT 3 AS Id, 'Richard Dawkins' AS Name, 1941 AS Born, 2 AS CountryId, 'United Kingdom' AS CountryName, 6 AS BookId, 'An Appetite for Wonder: The Making of a Scientist' AS BookName";
var remainingHorsemen = new Dictionary<int, Person>();
connection.Query<Person, Country, Book, Person>(sql, (person, country, book) => {
//person
Person personEntity;
//trip
if (!remainingHorsemen.TryGetValue(person.Id, out personEntity))
{
remainingHorsemen.Add(person.Id, personEntity = person);
}
//country
if(personEntity.Residience == null)
{
if (country == null)
{
country = new Country { CountryName = "" };
}
personEntity.Residience = country;
}
//books
if(personEntity.Books == null)
{
personEntity.Books = new List<Book>();
}
if (book != null)
{
if (!personEntity.Books.Any(x => x.BookId == book.BookId))
{
personEntity.Books.Add(book);
}
}
return personEntity;
},
splitOn: "CountryId,BookId");
splitOn
引数が、次の型の最初の列のカンマ区切りリストであることに注意してください。
7種類以上のマッピング
マッピングするタイプの数が、構築を行うFunc <>によって提供される7を超えることがあります。
Query<>
にジェネリック型引数の入力を使用する代わりに、配列としてマッピングする型と、それに続くマッピング関数を提供します。初期のマニュアル設定や値のキャスト以外は、残りの機能は変更されません。
var sql = @"SELECT 1 AS Id, 'Daniel Dennett' AS Name, 1942 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 1 AS BookId, 'Brainstorms' AS BookName
UNION ALL SELECT 1 AS Id, 'Daniel Dennett' AS Name, 1942 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 2 AS BookId, 'Elbow Room' AS BookName
UNION ALL SELECT 2 AS Id, 'Sam Harris' AS Name, 1967 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 3 AS BookId, 'The Moral Landscape' AS BookName
UNION ALL SELECT 2 AS Id, 'Sam Harris' AS Name, 1967 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 4 AS BookId, 'Waking Up: A Guide to Spirituality Without Religion' AS BookName
UNION ALL SELECT 3 AS Id, 'Richard Dawkins' AS Name, 1941 AS Born, 2 AS CountryId, 'United Kingdom' AS CountryName, 5 AS BookId, 'The Magic of Reality: How We Know What`s Really True' AS BookName
UNION ALL SELECT 3 AS Id, 'Richard Dawkins' AS Name, 1941 AS Born, 2 AS CountryId, 'United Kingdom' AS CountryName, 6 AS BookId, 'An Appetite for Wonder: The Making of a Scientist' AS BookName";
var remainingHorsemen = new Dictionary<int, Person>();
connection.Query<Person>(sql,
new[]
{
typeof(Person),
typeof(Country),
typeof(Book)
}
, obj => {
Person person = obj[0] as Person;
Country country = obj[1] as Country;
Book book = obj[2] as Book;
//person
Person personEntity;
//trip
if (!remainingHorsemen.TryGetValue(person.Id, out personEntity))
{
remainingHorsemen.Add(person.Id, personEntity = person);
}
//country
if(personEntity.Residience == null)
{
if (country == null)
{
country = new Country { CountryName = "" };
}
personEntity.Residience = country;
}
//books
if(personEntity.Books == null)
{
personEntity.Books = new List<Book>();
}
if (book != null)
{
if (!personEntity.Books.Any(x => x.BookId == book.BookId))
{
personEntity.Books.Add(book);
}
}
return personEntity;
},
splitOn: "CountryId,BookId");
カスタムマッピング
クエリの列名がクラスと一致しない場合は、型のマッピングを設定できます。この例は、カスタムマッピングと同様にSystem.Data.Linq.Mapping.ColumnAttribute
を使用したマッピングを示しています。
マッピングは、タイプごとに1回だけ設定する必要があるため、アプリケーションの起動時に設定するか、または1回だけ初期化されるように設定します。
One-to-Manyの例と同じクエリを再度仮定し、クラスをより良い名前にリファクタリングします。
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Born { get; set; }
public Country Residience { get; set; }
public ICollection<Book> Books { get; set; }
}
public class Country
{
[System.Data.Linq.Mapping.Column(Name = "CountryId")]
public int Id { get; set; }
[System.Data.Linq.Mapping.Column(Name = "CountryName")]
public string Name { get; set; }
}
public class Book
{
public int Id { get; set; }
public string Name { get; set; }
}
Book
がColumnAttribute
依存しない方法に注意してください。ただし、if
ステートメントを維持する必要があります
今度はこのマッピングコードをアプリケーションのどこかに置いて、一度だけ実行します:
Dapper.SqlMapper.SetTypeMap(
typeof(Country),
new CustomPropertyTypeMap(
typeof(Country),
(type, columnName) =>
type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.OfType<System.Data.Linq.Mapping.ColumnAttribute>()
.Any(attr => attr.Name == columnName)))
);
var bookMap = new CustomPropertyTypeMap(
typeof(Book),
(type, columnName) =>
{
if(columnName == "BookId")
{
return type.GetProperty("Id");
}
if (columnName == "BookName")
{
return type.GetProperty("Name");
}
throw new InvalidOperationException($"No matching mapping for {columnName}");
}
);
Dapper.SqlMapper.SetTypeMap(typeof(Book), bookMap);
その後、以前のQuery<>
例のいずれかを使用してクエリが実行されます。
この回答には、マッピングを追加する簡単な方法が示されています 。