Situé à https://github.com/dapperlib/dapper/releases
Myget Pre-Release Feed: https://www.myget.org/gallery/dapper
Emballer | Nuget stable | Nuget Pre-Release | Téléchargements | Myget |
---|---|---|---|---|
Pimpant | ||||
Dapper.EntityFramework | ||||
Dapper.entityframework.strongname | ||||
Dapper.Rainbow | ||||
Dapper.sqlbuilder | ||||
Dapper.strongname |
BUTS DE Package:
Dapper a été développé à l'origine pour et par un débordement de pile, mais est f / oss. Le parrainage est le bienvenu et invité - voir le lien du sponsor en haut de la page. Un grand merci à tous (individus ou organisations) qui ont parrainé le dapper, mais un grand merci en particulier à:
Dapper est une bibliothèque NuGet que vous pouvez ajouter à votre projet qui améliorera vos connexions ADO.NET via des méthodes d'extension sur votre instance DbConnection
. Cela fournit une API simple et efficace pour invoquer SQL, avec un support pour l'accès aux données synchrones et asynchrones, et permet des requêtes tamponnées et non tamponnées.
Il fournit plusieurs aides, mais les API clés sont:
// insert/update/delete etc
var count = connection . Execute ( sql [ , args ] ) ;
// multi-row query
IEnumerable < T > rows = connection . Query < T > ( sql [ , args ] ) ;
// single-row query ({Single|First}[OrDefault])
T row = connection . QuerySingle < T > ( sql [ , args ] ) ;
où args
peuvent être (entre autres):
Dictionary<string,object>
DynamicParameters
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 ) ;
Cette méthode exécutera SQL et renverra une liste dynamique.
Exemple d'utilisation:
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 ) ;
Exemple d'utilisation:
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 ) ;
La même signature vous permet également d'exécuter plusieurs fois une commande de manière pratique et efficace (par exemple aux données de charge en vrac)
Exemple d'utilisation:
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 rows inserted: "1,1", "2,2" and "3,3"
Un autre exemple d'utilisation lorsque vous avez déjà une collection existante:
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 ) ;
Cela fonctionne pour tout paramètre qui implémente IEnumerable<T>
pour certains T.
Une caractéristique clé de Dapper est les performances. Les mesures suivantes montrent combien de temps il faut pour exécuter une instruction SELECT
contre une base de données (dans diverses configts, chacune étiquetée) et cartographier les données renvoyées aux objets.
Les repères peuvent être trouvés dans dapper.tests.performance (les contributions sont les bienvenues!) Et peuvent être exécutées via:
dotnet run --project . b enchmarks D apper.Tests.Performance -c Release -f net8.0 -- -f * --join
La sortie de la dernière course est:
BenchmarkDotNet v0.13.7, Windows 10 (10.0.19045.3693/22H2/2022Update)
Intel Core i7-3630QM CPU 2.40GHz (Ivy Bridge), 1 CPU, 8 logical and 4 physical cores
.NET SDK 8.0.100
[Host] : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX
ShortRun : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX
Orm | Méthode | Retour | Signifier | Stddev | Erreur | Gen0 | Gen1 | Gen2 | Alloué |
---|---|---|---|---|---|---|---|---|---|
Impact du cache élégant | ExecuteParameters_cache | Vide | 96.75 US | 0,668 US | 1.010 US | 0,6250 | - | - | 2184 B |
Impact du cache élégant | Queryfirstparameters_cache | Vide | 96.86 US | 0,493 US | 0,746 US | 0,8750 | - | - | 2824 B |
Codé à la main | Sqlcommand | Poste | 119,70 États-Unis | 0,706 US | 1.067 US | 1.3750 | 1,0000 | 0,1250 | 7584 b |
Codé à la main | DataTable | dynamique | 126,64 États-Unis | 1.239 US | 1.873 US | 3,0000 | - | - | 9576 B |
Sqlmarshal | Sqlcommand | Poste | 132.36 US | 1,008 US | 1,523 US | 2,0000 | 1,0000 | 0,2500 | 11529 b |
Pimpant | Queryfirstordefault | Poste | 133,73 États-Unis | 1.301 US | 2.186 US | 1.7500 | 1,5000 | - | 11608 B |
Puissant | Requête | dynamique | 133,92 États-Unis | 1.075 US | 1.806 US | 2,0000 | 1.7500 | - | 12710 b |
Linq à db | Requête | Poste | 134.24 US | 1.068 US | 1.614 US | 1.7500 | 1.2500 | - | 10904 b |
Rospodb | Executequery | Poste | 135.83 US | 1.839 US | 3.091 US | 1.7500 | 1,5000 | - | 11649 b |
Pimpant | 'Requête (tamponnée)' | Poste | 136.14 US | 1,755 US | 2.653 US | 2,0000 | 1,5000 | - | 11888 b |
Puissant | Requête | Poste | 137,96 États-Unis | 1.485 US | 2.244 US | 2.2500 | 1.2500 | - | 12201 B |
Pimpant | Queryfirstordefault | dynamique | 139.04 US | 1,507 US | 2.279 US | 3,5000 | - | - | 11648 b |
Puissant | Singlefromquery | dynamique | 139,74 États-Unis | 2.521 US | 3.811 US | 2,0000 | 1.7500 | - | 12710 b |
Pimpant | 'Requête (tamponnée)' | dynamique | 140.13 US | 1.382 US | 2.090 US | 2,0000 | 1,5000 | - | 11968 B |
Servicestack | De punition unique | Poste | 140,76 États-Unis | 1.147 US | 2.192 US | 2,5000 | 1.2500 | 0,2500 | 15248 b |
Pimpant | 'Contriber' | Poste | 141.09 US | 1.394 US | 2.108 US | 2,0000 | 1,5000 | - | 12440 b |
Puissant | Singlefromquery | Poste | 141.17 US | 1.941 US | 2,935 US | 1.7500 | 1,5000 | - | 12201 B |
Massif | «Requête (dynamique)» | dynamique | 142.01 US | 4.957 US | 7.494 US | 2,0000 | 1,5000 | - | 12342 b |
Linq à db | 'Premier (compilé)' | Poste | 144.59 US | 1.295 US | 1,958 US | 1.7500 | 1,5000 | - | 12128 B |
Rospodb | Queryfield | Poste | 148.31 US | 1.742 US | 2.633 US | 2,0000 | 1,5000 | 0,5000 | 13938 b |
Norme | 'Lire <> (tuples)' | Valuetuple`8 | 148,58 États-Unis | 2.172 US | 3.283 US | 2,0000 | 1.7500 | - | 12745 b |
Norme | 'Lire <()> (nommé tuples)' | Valuetuple`8 | 150,60 États-Unis | 0,658 US | 1.106 US | 2.2500 | 2,0000 | 1.2500 | 14562 b |
Rospodb | Requête | Poste | 152.34 US | 2.164 US | 3.271 US | 2.2500 | 1,5000 | 0,2500 | 14106 b |
Rospodb | Querydynynamic | Poste | 154.15 US | 4.108 US | 6.210 US | 2.2500 | 1.7500 | 0,5000 | 13930 b |
Rospodb | Requête | Poste | 155.90 US | 1.953 US | 3.282 US | 2,5000 | 0,5000 | - | 14858 b |
Impact du cache élégant | Executeoparameters_Nocache | Vide | 162.35 US | 1,584 US | 2.394 US | - | - | - | 760 b |
Impact du cache élégant | ExecutenOparameters_cache | Vide | 162.42 US | 2.740 US | 4.142 US | - | - | - | 760 b |
Impact du cache élégant | Queryfirstnoparameters_cache | Vide | 164.35 US | 1.206 US | 1.824 US | 0,2500 | - | - | 1520 b |
Devexpress.xpo | FindObject | Poste | 165.87 US | 1.012 US | 1.934 US | 8.5000 | - | - | 28099 b |
Impact du cache élégant | Queryfirstnoparameters_nocache | Vide | 173,87 États-Unis | 1.178 US | 1.781 US | 0,5000 | - | - | 1576 b |
Linq à db | D'abord | Poste | 175.21 US | 2.292 US | 3.851 US | 2,0000 | 0,5000 | - | 14041 b |
EF 6 | Sqlquery | Poste | 175.36 US | 2.259 US | 3.415 US | 4.0000 | 0,7500 | - | 24209 b |
Norme | 'Lire <> (classe)' | Poste | 186.37 US | 1.305 US | 2.496 US | 3,0000 | 0,5000 | - | 17579 b |
Devexpress.xpo | GetObjectbykey | Poste | 186.78 US | 3.407 US | 5.151 US | 4.5000 | 1,0000 | - | 30114 b |
Pimpant | 'Requête (à mal à bout)' | dynamique | 194.62 US | 1.335 US | 2.019 US | 1.7500 | 1,5000 | - | 12048 b |
Pimpant | 'Requête (à mal à bout)' | Poste | 195.01 US | 0,888 US | 1.343 US | 2,0000 | 1,5000 | - | 12008 b |
Devexpress.xpo | Requête | Poste | 199,46 États-Unis | 5.500 US | 9.243 US | 10.0000 | - | - | 32083 B |
Belgrader | FirstOrdefault | Tâche 1 | 228.70 US | 2.181 US | 3.665 US | 4.5000 | 0,5000 | - | 20555 b |
Noyau EF | 'Premier (compilé)' | Poste | 265.45 US | 17.745 US | 26.828 US | 2,0000 | - | - | 7521 b |
Nhibernate | Obtenir | Poste | 276.02 US | 8.029 US | 12.139 US | 6.5000 | 1,0000 | - | 29885 b |
Nhibernate | HQL | Poste | 277.74 US | 13.032 US | 19.703 US | 8.0000 | 1,0000 | - | 31886 b |
Nhibernate | Critères | Poste | 300,22 États-Unis | 14.908 US | 28.504 US | 13h0000 | 1,0000 | - | 57562 B |
EF 6 | D'abord | Poste | 310.55 US | 27.254 US | 45.799 US | 13h0000 | - | - | 43309 b |
Noyau EF | D'abord | Poste | 317.12 US | 1.354 US | 2.046 US | 3,5000 | - | - | 11306 b |
Noyau EF | Sqlquery | Poste | 322.34 US | 23.990 US | 40.314 US | 5.0000 | - | - | 18195 b |
Nhibernate | SQL | Poste | 325.54 US | 3.937 US | 7.527 US | 22.0000 | 1,0000 | - | 80007 b |
EF 6 | 'Premier (pas de suivi)' | Poste | 331.14 US | 27.760 US | 46.649 US | 12h0000 | 1,0000 | - | 50237 b |
Noyau EF | 'Premier (pas de suivi)' | Poste | 337.82 US | 27.814 US | 46.740 US | 3,0000 | 1,0000 | - | 17986 b |
Nhibernate | Linq | Poste | 604.74 US | 5.549 US | 10.610 US | 10.0000 | - | - | 46061 b |
Impact du cache élégant | ExecuteParameters_Nocache | Vide | 623.42 US | 3.978 US | 6.684 US | 3,0000 | 2,0000 | - | 10001 b |
Impact du cache élégant | Queryfirstparameters_nocache | Vide | 630.77 US | 3.027 US | 4.576 US | 3,0000 | 2,0000 | - | 10640 b |
N'hésitez pas à soumettre des correctifs qui incluent d'autres ORM - lors de l'exécution de repères, assurez-vous de compiler dans la version et de ne pas attacher un débogueur ( CTRL + F5 ).
Alternativement, vous préférez peut-être la suite de tests RawDataAccessBencher de Frans Bouma ou Ormbenchmark.
Les paramètres sont généralement passés en tant que classes anonymes. Cela vous permet de nommer facilement vos paramètres et vous donne la possibilité de simplement couper des extraits SQL et de les exécuter dans l'analyseur de requête de votre plateforme DB.
new { A = 1 , B = " b " } // A will be mapped to the param @A, B to the param @B
Les paramètres peuvent également être construits dynamiquement à l'aide de la classe DynamicParameters. Cela permet de construire une instruction SQL dynamique tout en utilisant des paramètres pour la sécurité et les performances.
var sqlPredicates = new List < string > ( ) ;
var queryParams = new DynamicParameters ( ) ;
if ( boolExpression )
{
sqlPredicates . Add ( " column1 = @param1 " ) ;
queryParams . Add ( " param1 " , dynamicValue1 , System . Data . DbType . Guid ) ;
} else {
sqlPredicates . Add ( " column2 = @param2 " ) ;
queryParams . Add ( " param2 " , dynamicValue2 , System . Data . DbType . String ) ;
}
DynamicParameters prend également en charge la copie de plusieurs paramètres à partir d'objets existants de différents types.
var queryParams = new DynamicParameters ( objectOfType1 ) ;
queryParams . AddDynamicParams ( objectOfType2 ) ;
Lorsqu'un objet qui implémente l'interface IDynamicParameters
passée dans les fonctions Execute
ou Query
, les valeurs de paramètres seront extraites via cette interface. De toute évidence, la classe d'objets la plus probable à utiliser à cet effet serait la classe DynamicParameters
intégrée.
Dapper vous permet de passer dans IEnumerable<int>
et va automatiquement paramétrisera votre requête.
Par exemple:
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 } } ) ;
Sera traduit en:
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 prend en charge les remplacements littéraux pour les types de bool et numériques.
connection . Query ( " select * from User where UserTypeId = {=Admin} " , new { UserTypeId . Admin } ) ;
Le remplacement littéral n'est pas envoyé sous forme de paramètre; Cela permet de meilleurs plans et une utilisation de l'indice filtré, mais doit généralement être utilisé avec parcimonie et après les tests. Cette fonctionnalité est particulièrement utile lorsque la valeur injectée est en fait une valeur fixe (par exemple, un "ID de catégorie", "code d'état" ou "région" fixe qui est spécifique à la requête). Pour les données en direct où vous envisagez des littéraux, vous pouvez également envisager et tester des conseils de requête spécifiques aux fournisseurs comme OPTIMIZE FOR UNKNOWN
avec des paramètres réguliers.
Le comportement par défaut de Dapper consiste à exécuter votre SQL et à tamponner le lecteur entier à retour. Ceci est idéal dans la plupart des cas car il minimise les verrous partagés dans la base de données et réduit le temps du réseau DB.
Cependant, lors de l'exécution d'énormes requêtes, vous devrez peut-être minimiser l'empreinte de la mémoire et charger uniquement les objets au besoin. Pour ce faire passer, buffered: false
dans la méthode Query
.
Dapper vous permet de cartographier une seule ligne sur plusieurs objets. Ceci est une caractéristique clé si vous souhaitez éviter les associations de requête et de charge impatientes étrangères.
Exemple:
Considérez 2 classes: Post
et 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 ; }
}
Disons maintenant que nous voulons cartographier une requête qui rejoint à la fois les publications et la table des utilisateurs. Jusqu'à présent, si nous devions combiner le résultat de 2 requêtes, nous aurions besoin d'un nouvel objet pour l'exprimer, mais il est plus logique dans ce cas de mettre l'objet User
à l'intérieur de l'objet Post
.
Ceci est le cas d'utilisation du multitographie. Vous dites à Dapper que la requête renvoie un Post
et un objet User
, puis lui donne une fonction décrivant ce que vous voulez faire avec chacune des lignes contenant à la fois un Post
et un objet User
. Dans notre cas, nous voulons prendre l'objet utilisateur et le mettre à l'intérieur de l'objet post. Nous écrivons donc la fonction:
( post , user ) => { post . Owner = user ; return post ; }
Les arguments à 3 types dans la méthode Query
spécifient quels objets doivent utiliser pour désérialiser la ligne et ce qui va être renvoyé. Nous allons interpréter les deux lignes comme une combinaison de Post
et User
et nous retournons un objet Post
. Par conséquent, la déclaration de type devient
< Post , User , Post >
Tout est réunis, ressemble à ceci:
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 . Equal ( 1 , post . Id ) ;
Assert . Equal ( " Sam " , post . Owner . Name ) ;
Assert . Equal ( 99 , post . Owner . Id ) ;
Dapper est capable de diviser la ligne renvoyée en supposant que vos colonnes ID sont nommées Id
ou id
. Si votre clé principale est différente ou si vous souhaitez diviser la ligne à un point autre que Id
, utilisez le paramètre splitOn
en option.
Dapper vous permet de traiter plusieurs grilles de résultats dans une seule requête.
Exemple:
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 soutient entièrement les Procs stockés:
var user = cnn . Query < User > ( " spGetUser " , new { Id = 1 } ,
commandType : CommandType . StoredProcedure ) . SingleOrDefault ( ) ;
Si vous voulez quelque chose de plus sophistiqué, vous pouvez faire:
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 " ) ;
Dapper prend en charge VarChar Params, si vous exécutez une clause WHERE sur une colonne VARCHAR à l'aide d'un param, assurez-vous de le passer de cette manière:
Query < Thing > ( " select * from Thing where Name = @Name " , new { Name = new DbString { Value = " abcde " , IsFixedLength = true , Length = 10 , IsAnsi = true } } ) ;
Sur SQL Server, il est crucial d'utiliser l'Unicode lors de l'interrogation Unicode et ANSI lors de l'interrogation non Unicode.
Habituellement, vous voudrez traiter toutes les lignes à partir d'une table donnée comme le même type de données. Cependant, il existe certaines circonstances où il est utile de pouvoir analyser différentes lignes en différents types de données. C'est là que IDataReader.GetRowParser
est utile.
Imaginez que vous avez une table de base de données nommée "Formes" avec les colonnes: Id
, Type
et Data
, et vous souhaitez analyser ses lignes dans des objets Circle
, Square
ou Triangle
en fonction de la valeur de la colonne de type.
var shapes = new List < IShape > ( ) ;
using ( var reader = connection . ExecuteReader ( " select * from Shapes " ) )
{
// Generate a row parser for each type you expect.
// The generic type <IShape> is what the parser will return.
// The argument (typeof(*)) is the concrete type to parse.
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 ) ;
}
}
Afin d'utiliser des variables SQL non paramètres avec le connecteur MySQL, vous devez ajouter l'option suivante à votre chaîne de connexion:
Allow User Variables=True
Assurez-vous de ne pas fournir à Dapper une propriété à cartographier.
Dapper cache des informations sur chaque requête qu'il exécute, ce qui lui permet de matérialiser rapidement des objets et de traiter rapidement les paramètres. La mise en œuvre actuelle cache ces informations dans un objet ConcurrentDictionary
. Les déclarations qui ne sont utilisées qu'une seule fois sont systématiquement rincées à partir de ce cache. Pourtant, si vous générez des chaînes SQL à la volée sans utiliser de paramètres, il est possible que vous puissiez frapper les problèmes de mémoire.
La simplicité de Dapper signifie que de nombreuses fonctionnalités avec lesquelles Orms expédient sont éliminées. Il s'inquiète du scénario à 95% et vous donne les outils dont vous avez besoin la plupart du temps. Il n'essaie pas de résoudre tous les problèmes.
Dapper n'a pas de détails d'implémentation spécifiques à la base de données, il fonctionne sur tous les fournisseurs ADO .NET, y compris SQLite, SQL CE, Firebird, Oracle, MariADB, MySQL, PostgreSQL et SQL Server.
Dapper propose une suite de tests complète dans le projet de test.
Dapper est dans l'utilisation de la production chez Stack Overflow.