Dapper.NET
Multimapa
Buscar..
Sintaxis
-
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)
Parámetros
Parámetro | Detalles |
---|---|
cnn | Su conexión de base de datos, que ya debe estar abierta. |
sql | Comando para ejecutar. |
tipos | Conjunto de tipos en el conjunto de registros. |
mapa | Func<> que maneja la construcción del resultado de retorno. |
param | Objeto para extraer parámetros de. |
transacción | Transacción de la que forma parte esta consulta, si la hubiera. |
amortiguado | Ya sea para almacenar o no los resultados de la consulta. Este es un parámetro opcional con el valor predeterminado verdadero. Cuando el búfer es verdadero, los resultados se guardan en una List<T> y luego se devuelven como IEnumerable<T> que es seguro para la enumeración múltiple. Cuando el búfer es falso, la conexión SQL se mantiene abierta hasta que finalice la lectura, lo que le permite procesar una sola fila en el momento en la memoria. Las enumeraciones múltiples generarán conexiones adicionales a la base de datos. Si bien el búfer falso es altamente eficiente para reducir el uso de memoria, si solo mantiene fragmentos muy pequeños de los registros devueltos, tiene una sobrecarga de rendimiento considerable en comparación con materializar con impaciencia el conjunto de resultados. Por último, si tiene numerosas conexiones de SQL no búfer concurrentes, debe considerar que la inanición de la agrupación de conexiones provoca que las solicitudes se bloqueen hasta que las conexiones estén disponibles. |
dividido en | El campo del que deberíamos dividir y leer el segundo objeto (por defecto: id). Esta puede ser una lista delimitada por comas cuando más de 1 tipo está contenido en un registro. |
commandTimeout | Número de segundos antes del tiempo de espera de ejecución del comando. |
commandType | ¿Es un proceso almacenado o un lote? |
Mapeo simple multi-mesa
Digamos que tenemos una consulta de los jinetes restantes que necesitan poblar una clase de Persona.
Nombre | Nacido | Residencia |
---|---|---|
Daniel Dennett | 1942 | Estados Unidos de America |
Sam Harris | 1967 | Estados Unidos de America |
Richard Dawkins | 1941 | Reino Unido |
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; }
}
Podemos rellenar la clase de persona y la propiedad Residencia con una instancia de País usando una Query<>
sobrecarga Query<>
que toma una función Func<>
que se puede usar para componer la instancia devuelta. El Func<>
puede tomar hasta 7 tipos de entrada con el argumento genérico final siempre siendo el tipo de retorno.
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");
Tenga en cuenta el uso del
splitOn: "Residence"
que es la primera columna del siguiente tipo de clase que se completará (en este caso,Country
). Dapper buscará automáticamente una columna llamada Id para dividir, pero si no encuentra una ysplitOn
no se proporciona, sesplitOn
unasplitOn
System.ArgumentException
con un mensaje útil. Entonces, aunque es opcional, normalmente tendrá que proporcionar un valor desplitOn
.
Mapeo uno a muchos
Veamos un ejemplo más complejo que contiene una relación de uno a varios. Nuestra consulta ahora contendrá varias filas que contienen datos duplicados y tendremos que manejar esto. Hacemos esto con una búsqueda en un cierre.
La consulta cambia ligeramente al igual que las clases de ejemplo.
Carné de identidad | Nombre | Nacido | PaísId | Nombre del país | BookId | Nombre del libro |
---|---|---|---|---|---|---|
1 | Daniel Dennett | 1942 | 1 | Estados Unidos de America | 1 | Tormentas de ideas |
1 | Daniel Dennett | 1942 | 1 | Estados Unidos de America | 2 | Cuarto de la esquina |
2 | Sam Harris | 1967 | 1 | Estados Unidos de America | 3 | El paisaje moral |
2 | Sam Harris | 1967 | 1 | Estados Unidos de America | 4 | Despertar: una guía a la espiritualidad sin religión |
3 | Richard Dawkins | 1941 | 2 | Reino Unido | 5 | La magia de la realidad: Cómo sabemos lo que es realmente cierto |
3 | Richard Dawkins | 1941 | 2 | Reino Unido | 6 | Un apetito por la maravilla: la creación de un científico |
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; }
}
Los remainingHorsemen
diccionario se rellenarán con instancias completamente materializadas de los objetos personales. Para cada fila del resultado de la consulta, se pasan los valores asignados de las instancias de los tipos definidos en los argumentos lambda y depende de usted cómo manejar esto.
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");
Observe que el argumento
splitOn
es una lista delimitada por comas de las primeras columnas del siguiente tipo.
Mapeo de más de 7 tipos
A veces, el número de tipos que está asignando excede los 7 proporcionados por la Función <> que hace la construcción.
En lugar de utilizar la Query<>
con las entradas de argumento de tipo genérico, proporcionaremos los tipos para asignar como una matriz, seguido por la función de asignación. Aparte del ajuste manual inicial y la conversión de los valores, el resto de la función no cambia.
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");
Asignaciones personalizadas
Si los nombres de las columnas de consulta no coinciden con sus clases, puede configurar asignaciones para tipos. Este ejemplo muestra la asignación utilizando System.Data.Linq.Mapping.ColumnAttribute
, así como una asignación personalizada.
Las asignaciones solo se deben configurar una vez por tipo, así que configúrelas en el inicio de la aplicación o en algún otro lugar para que solo se inicialicen una vez.
Suponiendo de nuevo la misma consulta que el ejemplo Uno a muchos y las clases rediseñadas para obtener mejores nombres como:
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; }
}
Observe cómo
Book
no se basa enColumnAttribute
pero tendríamos que mantener la instrucciónif
Ahora coloque este código de mapeo en algún lugar de su aplicación donde solo se ejecute una vez:
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);
Luego, la consulta se ejecuta utilizando cualquiera de los ejemplos anteriores de Query<>
.
Una forma más sencilla de agregar las asignaciones se muestra en esta respuesta .