Расположены по адресу dapperlib.github.io/Dapper.
Предварительный канал MyGet: https://www.myget.org/gallery/dapper
Пакет | NuGet Stable | NuGet Pre-release | Загрузки | MyGet |
---|---|---|---|---|
Dapper | ||||
Dapper.EntityFramework | ||||
Dapper.EntityFramework.StrongName | ||||
Dapper.Rainbow | ||||
Dapper.SqlBuilder | ||||
Dapper.StrongName |
Примечание: в запросе не удалось определить основной язык текста, поэтому перевод выполнен на русский язык. Dapper — библиотека NuGet, расширяющая интерфейс IDbConnection
Dapper — это библиотека NuGet, которую можно добавить в свой проект. Она расширяет интерфейс IDbConnection
.
Возможности
Выполнение запроса и сопоставление результатов со строго типизированным списком
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
Пример использования:
public class Dog
{
public int? Age { get; set; }
public Guid Id { get; set; }
public string Name { get; set; }
public float? Weight { get; set; }
public int IgnoredProperty { get { return 1; } }
}
var guid = Guid.NewGuid();
var dog = connection.Query<Dog>("select Age = @Age, Id = @Id", new { Age = (int?)null, Id = guid });
Assert.Equal(1,dog.Count());
Assert.Null(dog.First().Age);
Assert.Equal(guid, dog.First().Id);
Выполнение запроса и сопоставление его с динамическим списком объектов
public static IEnumerable<dynamic> Query (this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
Этот метод выполняет SQL и возвращает динамический список.
Пример использования:
var rows = connection.Query("select 1 A, 2 B union all select 3, 4").AsList();
Assert.Equal(1, (int)rows[0].A);
Assert.Equal(2, (int)rows[0].B);
Assert.Equal(3, (int)rows[1].A);
Assert.Equal(4, (int)rows[1].B);
Выполнение команды, которая не возвращает результатов
public static int Execute(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
Пример использования:
var count = connection.Execute(@"
set nocount on
create table #t(i int)
set nocount off
insert #t
select @a a union all select @b
set nocount on
drop table #t", new {a=1, b=2 });
Assert.Equal(2, count);
Удобное и эффективное выполнение команды несколько раз
Та же сигнатура также позволяет удобно и эффективно выполнять команду несколько раз (например, для массовой загрузки данных).
Пример использования:
var count = connection.Execute(@"insert MyTable(colA, colB) values (@a, @b)",
new[] { new { a=1, b=1 }, new { a=2, b=2 }, new { a=3, b=3 } }
);
Assert.Equal(3, count); // 3 строки вставлены: "1,1", "2,2" и "3,3"
Ещё один пример использования, когда у вас уже есть существующая коллекция:
var foos = new List<Foo>
{
{ new Foo { A = 1, B = 1 } }
{ new Foo { A = 2, B = 2 } }
{ new Foo { A = 3, B = 3 } }
};
var count = connection.Execute(@"insert MyTable(colA, colB) values (@a, @b)", foos);
Assert.Equal(foos.Count, count);
Это работает для любого параметра, который реализует IEnumerable<T>
для некоторого T
.
Производительность
Ключевой особенностью Dapper является производительность. В следующих метриках показано, сколько времени требуется для выполнения оператора SELECT
по отношению к БД (в различных конфигурациях, каждая помечена) и сопоставления возвращённых данных с объектами.
Тесты можно найти в Dapper.Tests.Performance (приветствуются вклады!) и запустить через:
dotnet run -p .\benchmarks\Dapper.Tests.Performance\ -c Release -f netcoreapp3.1 -- -f * --join
Вывод последнего запуска:
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.208 | ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated |
|--------------- |------------------------------ |-------- |----------:|----------:|----------:|--------:|-------:|-------:|----------:|
| Belgrade | ExecuteReader | Post | 94,46 мкс | 8,115 мкс | 12,268 мкс | 1,7500 | 0,5000 | - | 8,42 КБ |
| Hand Coded | DataTable | dynamic | 105,43 мкс | 0,998 мкс | 1,508 мкс | 3,0000 | - | - | 9,37 КБ |
| Hand Coded | SqlCommand | Post | 106,58 мкс | 1,191 мкс | 1,801 мкс | 1,5000 | 0,7500 | 0,1250 | 7,42 КБ |
| Dapper | QueryFirstOrDefault<dynamic> | dynamic | 119,52 мкс | 1,320 мкс | 2,219 мкс | 3,6250 | - | - | 11,39 КБ |
| Dapper | 'Query<dynamic> (buffered)' | dynamic | 119,93 мкс | 1,943 мкс | 2,937 мкс | 2,3750 | 1,0000 | 0,2500 | 11,73 КБ |
| Massive | 'Query (dynamic)' | dynamic | 120,31 мкс | 1,340 мкс | 2,252 мкс | 2,2500 | 1,0000 | 0,1250 | 12,07 КБ |
| Dapper | QueryFirstOrDefault<T> | Post | 121,57 мкс | 1,564 мкс | 2,364 мкс | 1,7500 | 0,7500 | - | 11,35 КБ |
| Dapper | 'Query<T> (buffered)' | Post | 121,67 мкс | 2,913 мкс | 4,403 мкс | 1,8750 | 0,8750 | - | 11,65 КБ |
| PetaPoco | 'Fetch<T> (Fast)' | Post | 124,91 мкс | 4,015 мкс | 6,747 мкс | 2,0000 | 1,0000 | - | 11,5 КБ |
| Mighty | Query<T> | Post | 125,23 мкс | 2,932 мкс | 4,433 мкс | 2,2500 | 1,0000 | - | 12,6 КБ |
| LINQ to DB | Query<T> | Post | 125,76 мкс | 2,038 мкс | 3,081 мкс | 2,2500 | 0,7500 | 0,2500 | 10,62 КБ |
| PetaPoco | Fetch<T> | Post | 127,48 мкс | 4,283 мкс | 6,475 мкс | 2,0000 | 1,0000 | - | 12,18 КБ |
| LINQ to DB | 'First (Compiled)' | Post | 128,89 мкс | 2,627 мкс | 3,971 мкс | 2,5000 | 0,7500 | - | 10,92 КБ |
| Mighty | Query<dynamic> | dynamic | 129,20 мкс | 2,577 мкс | 3,896 мкс | 2,0000 | 1,0000 | - | 12,43 КБ |
| Mighty | SingleFromQuery<T> | Post | 129,41 мкс | 2,094 мкс | 3,166 мкс | 2,2500 | 1,0000 | - | 12,6 KB |
| Mighty | SingleFromQuery<dynamic> | dynamic | 130,59 мкс | 2,432 мкс | 3,677 мкс | 2,0000 | 1,0000 | - | 12,43 KB |
| Dapper | 'Contrib Get<T>' | Post | 134,74 мкс | 1,816 мкс | 2,746 мкс | 2,5000 | 1,0000 | 0,2500 | 12,29 KB |
| ServiceStack | SingleById<T> | Post | 135,01 мкс | 1,213 мкс | 2,320 мкс | 3,0000 | 1,0000 | 0,2500 | 15,27 KB |
| LINQ to DB | First | Post | 151,87 мкс | 3,826 мкс | 5,784 мкс | 3,0000 | 1,0000 | 0,2500 | 13,97 KB |
| EF 6 | SqlQuery | Post | 171,00 мкс | 1,460 мкс | 2,791 мкс | 3,7500 | 1,0000 | - | 23,67 KB |
| DevExpress.XPO | GetObjectByKey<T> | Post | 172,36 мкс | 3,758 мкс | 5,681 мкс | 5,5000 | 1,2500 | - | 29,06 KB |
| Dapper | 'Query<T> (unbuffered)' | Post | 174,40 мкс | 3,296 мкс | 4,983 мкс | 2,0000 | 1,0000 | - | 11,77 KB |
| Dapper | 'Query<dynamic> (unbuffered)' | dynamic | 174,45 мкс | 1,988 мкс | 3,340 мкс | 2,0000 | 1,0000 | - | 11,81 KB |
| DevExpress.XPO | FindObject<T> | Post | 181,76 мкс | 5,554 мкс | 9,333 мкс | 8,0000 | - | - | 27,15 KB |
| DevExpress.XPO | Query<T> | Post | 189,81 мкс | 4,187 мкс | 8,004 мкс | 10,0000 | - | - | 31,61 КБ |
| EF Core | 'First (Compiled)' | Post | 199,72 мкс | 3,983 мкс | 7,616 мкс | 4,5000 | - | - | 13,8 КБ |
| NHibernate | Get<T> | Post | 248,71 мкс | 6,604 мкс | 11,098 мкс | 5,0000 | 1,0000 | - | 29,79 КБ |
| EF Core | First | Post | 253,20 мкс | 3,033 мкс | 5,097 мкс | 5,5000 | - | - | 17,7 КБ |
| NHibernate | HQL | Post | 258,70 мкс | 11,716 мкс | 17,712 мкс | 5,0000 | 1,0000 | - | 32,1 КБ |
| EF Core | SqlQuery | Post | 268,89 мкс | 19,349 мкс | 32,516 мкс | 6,0000 | - | - | 18,5 КБ |
| EF 6 | First | Post | 278,46 мкс | 12,094 мкс | 18,284 мкс | 13,5000 | - | - | 44,18 КБ |
| EF Core | 'First (No Tracking)' | Post | 280,88 мкс | 8,192 мкс | 13,765 мкс | 3,0000 | 0,5000 | - | 19,38 КБ |
| NHibernate | Criteria | Post | 304,90 мкс | 2,232 мкс | 4,267 мкс | 11,0000 | 1,0000 | - | 60,29 КБ |
| EF 6 | 'First (No Tracking)' | Post | 316,55 мкс | 7,667 мкс | 11,592 мкс | 8,5000 | 1,0000 | - | 50,95 КБ |
| NHibernate | SQL | Post | 335,41 мкс | 3,111 мкс | 4,703 мкс | 19,0000 | 1,0000 | - | 78,86 КБ |
| NHibernate | LINQ | Post | 807,79 мкс | 27,207 мкс | 45,719 мкс | 8,0000 | 2,0000 | - | 53,65 КБ |
Feel free to submit patches that include other ORMs - when running benchmarks, be sure to compile in Release and not attach a debugger (<kbd>Ctrl</kbd>+<kbd>F5</kbd>).
Alternatively, you might prefer Frans Bouma's [RawDataAccessBencher](https://github.com/FransBouma/RawDataAccessBencher) test suite or [OrmBenchmark](https://github.com/InfoTechBridge/OrmBenchmark).
**Параметры запроса**
Параметры передаются в виде анонимных классов. Это позволяет легко называть параметры и даёт возможность просто вырезать и вставлять фрагменты SQL и запускать их в анализаторе запросов платформы базы данных.
```csharp
new {A = 1, B = "b"} // A будет сопоставлен с параметром @A, B — с параметром @B
Поддержка списков
Dapper позволяет передавать IEnumerable<int>
и автоматически параметризует запрос.
Например:
connection.Query<int>("select * from (select 1 as Id union all select 2 union all select 3) as X where Id in @Ids", new { Ids = new int[] { 1, 2, 3 } });
Будет переведено на:
select * from (select 1 as Id union all select 2 union all select 3) as X where Id in (@Ids1, @Ids2, @Ids3)" // @Ids1 = 1 , @Ids2 = 2 , @Ids2 = 3
Литеральные замены
Dapper поддерживает литералы для типов bool и numeric.
connection.Query("select * from User where UserTypeId = {=Admin}", new { UserTypeId.Admin });
Литеральная замена не отправляется как параметр; это позволяет создавать более эффективные планы и использовать отфильтрованные индексы, но обычно её следует использовать экономно и после тестирования. Эта функция особенно полезна, когда значение, которое вводится, фактически является фиксированным значением (например, фиксированный «идентификатор категории», «код состояния» или «регион», который специфичен для запроса). Для живых данных, где вы рассматриваете литералы, вы также можете рассмотреть и протестировать специфические для провайдера подсказки, такие как OPTIMIZE FOR UNKNOWN
с обычными параметрами.
Буферизованные и небуферизованные считыватели
Поведение Dapper по умолчанию заключается в выполнении вашего SQL и буферизации всего считывателя при возврате. В большинстве случаев это идеально, поскольку минимизирует общие блокировки в базе данных и сокращает время работы сети.
Однако при выполнении огромных запросов вам может потребоваться минимизировать использование памяти и загружать объекты только по мере необходимости. Для этого передайте buffered: false
в метод Query
. Мультимаппинг
Dapper позволяет сопоставлять одну строку с несколькими объектами. Это ключевая функция, если вы хотите избежать лишних запросов и активной загрузки ассоциаций.
Пример:
Рассмотрим 2 класса: Post
и User
.
class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public User Owner { get; set; }
}
class User
{
public int Id { get; set; }
public string Name { get; set; }
}
Теперь предположим, что мы хотим сопоставить запрос, который объединяет таблицы posts
и users
. До сих пор, если нам нужно было объединить результаты двух запросов, нам нужен был новый объект, чтобы выразить это, но в этом случае имеет больше смысла поместить объект User
внутрь объекта Post
.
Это вариант использования мультимаппинга. Вы сообщаете Dapper, что запрос возвращает объекты Post
и User
, а затем предоставляете ему функцию, описывающую, что вы хотите сделать с каждой из строк, содержащих как объект Post
, так и объект User
. В нашем случае мы хотим взять объект пользователя и поместить его внутрь объекта поста. Поэтому мы пишем функцию:
(post, user) => { post.Owner = user; return post; }
Три аргумента типа для метода Query
определяют, какие объекты Dapper должен использовать для десериализации строки и что будет возвращено. Мы собираемся интерпретировать обе строки как комбинацию Post
и User
и возвращаем объект Post
. Следовательно, объявление типа становится
<Post, User, Post>
Всё вместе выглядит так:
var sql =
@"select * from #Posts p
left join #Users u on u.Id = p.OwnerId
Order by p.Id";
var data = connection.Query<Post, User, Post>(sql, (post, user) => { post.Owner = user; return post;});
var post = data.First();
Assert.Equal("Sams Post1", post.Content);
Assert.Assert(1, post.Id);
Assert.Equal("Sam", post.Owner.Name);
Assert.Equal(99, post.Owner.Id);
Dapper может разделить возвращаемую строку, предполагая, что ваши столбцы идентификаторов называются Id
или id
. Если ваш первичный ключ отличается или вы хотите разделить строку в точке, отличной от Id
, используйте необязательный параметр splitOn
.
Множественные результаты
Dapper позволяет обрабатывать несколько сеток результатов в одном запросе.
Пример:
var sql =
@"
select * from Customers where CustomerId = @id
select * from Orders where CustomerId = @id
select * from Returns where CustomerId = @id";
using (var multi = connection.QueryMultiple(sql, new {id=selectedId}))
{
var customer = multi.Read<Customer>().Single();
var orders = multi.Read<Order>().ToList();
var returns = multi.Read<Return>().ToList();
...
}
Хранимые процедуры
Dapper полностью поддерживает хранимые процедуры:
var user = cnn.Query<User>("spGetUser", new {Id = 1},
commandType: CommandType.StoredProcedure).SingleOrDefault();
Если вы хотите чего-то более сложного, вы можете сделать:
var p = new DynamicParameters();
p.Add("@a", 11);
p.Add("@b", dbType: DbType.Int32, direction: ParameterDirection.Output);
p.Add("@c", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue);
cnn.Execute("spMagicProc", p, commandType: CommandType.StoredProcedure);
int b = p.Get<int>("@b");
int c = p.Get<int>("@c");
Строки ANSI и varchar
Dapper поддерживает параметры varchar, если вы выполняете предложение where для столбца varchar с использованием параметра, обязательно передайте его следующим образом:
Query<Thing>("select * from Thing where Name = @Name", new {Name = new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = true });
На SQL Server крайне важно использовать unicode при запросе unicode и ANSI при запросе не unicode.
Переключение типов на строку
Обычно вы захотите рассматривать все строки из данной таблицы как один и тот же тип данных. Однако в некоторых случаях полезно иметь возможность анализировать разные строки как разные типы данных. Здесь пригодится IDataReader.GetRowParser
.
Представьте, что у вас есть таблица базы данных с именем «Shapes» со следующими данными: Столбцы: Id
, Type
и Data
, и вы хотите преобразовать их строки в объекты Circle
, Square
или Triangle
на основе значения столбца Type
.
var shapes = new List<IShape>();
using (var reader = connection.ExecuteReader("select * from Shapes"))
{
// Создаём парсер строк для каждого ожидаемого типа.
// Парсер будет возвращать общий тип <IShape>.
// Аргумент (typeof(*)) — это конкретный тип для анализа.
var circleParser = reader.GetRowParser<IShape>(typeof(Circle));
var squareParser = reader.GetRowParser<IShape>(typeof(Square));
var triangleParser = reader.GetRowParser<IShape>(typeof(Triangle));
var typeColumnIndex = reader.GetOrdinal("Type");
while (reader.Read())
{
IShape shape;
var type = (ShapeType)reader.GetInt32(typeColumnIndex);
switch (type)
{
case ShapeType.Circle:
shape = circleParser(reader);
break;
case ShapeType.Square:
shape = squareParser(reader);
break;
case ShapeType.Triangle:
shape = triangleParser(reader);
break;
default:
throw new NotImplementedException();
}
shapes.Add(shape);
}
}
Чтобы использовать непараметрические SQL-переменные с MySql Connector, необходимо добавить следующую опцию в строку подключения:
Allow User Variables=True
Убедитесь, что вы не предоставляете Dapper свойство для сопоставления.
Dapper кэширует информацию о каждом выполняемом запросе, что позволяет быстро материализовать объекты и быстро обрабатывать параметры. Текущая реализация кэширует эту информацию в объекте ConcurrentDictionary
. Заявления, которые используются только один раз, обычно удаляются из этого кэша. Тем не менее, если вы генерируете SQL-строки на лету без использования параметров, возможно, у вас могут возникнуть проблемы с памятью.
Простота Dapper означает, что многие функции, которыми обладают ORM, исключены. Он заботится о сценарии 95% и предоставляет вам инструменты, необходимые большую часть времени. Он не пытается решить все проблемы.
У Dapper нет специфичных для БД деталей реализации, он работает со всеми .NET ADO-провайдерами, включая SQLite, SQL CE, Firebird, Oracle, MySQL, PostgreSQL и SQL Server.
У Dapper есть полный набор тестов в тестовом проекте.
Dapper используется в производстве на Stack Overflow.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )