Dapper - .Net下的一个简单的ORM框架
特性
Dapper是一个NuGet库,你可以把它添加到你的项目中,扩展你的IDbConnection接口。
提供了3个方法
查询并映射一个实例集合
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null, bool buffered = true)
使用例子
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);
查询并映射成dynamic集合
public static IEnumerable<dynamic> Query (this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null, bool buffered = true)
使用例子
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);
执行无返回结果的sql语句
public static int Execute(this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = 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 rows inserted: "1,1", "2,2" and "3,3"
带参数查询
参数为一个匿名类
new {A = 1, B = "b"} // A will be mapped to the param @A, B to the param @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 });
多实例映射
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; } }
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);
假设做一个连接查询,将User对象放入Post对象更有意义。将user对象放入Post对象写法
(post, user) => { post.Owner = user; return post; }
Dapper可以通过假设Id列的名称为Id或Id来分割返回的行。如果主键不同,或者希望在Id以外的点分割行,可以使用可选的splitOn参数。
多结果查询
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(); }
存储过程
一般写法
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参数,如果你在varchar列上使用一个参数执行where子句,请确保以这样的方式传递它:
Query<Thing>("select * from Thing where Name = @Name", new {Name = new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = true });
在SQL Server上,查询unicode时使用unicode,查询非unicode时使用ANSI是至关重要的。
每行数据的类型切换
通常,您希望将给定表中的所有行视为相同的数据类型。但是,在某些情况下,能够将不同的行解析为不同的数据类型是很有用的。这是IDataReader的位置。GetRowParser可以派上用场。
假设您有一个名为“Shapes”的数据库表,列为:Id、Type和Data,您希望根据Type列的值将其行解析为Circle
、Square
或Triangle
对象。
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); } }
MySql中使用自定义变量
为 了在MySql中使用不带参数的sql,必须在连接字符串中使用Allow User Variables=True
限制与警告
Dapper会缓存运行查询结果,这允许它快速实体化对象和快速处理参数。当前实现将此信息缓存到ConcurrentDictionary对象中。只使用过一次的语句将定期从该缓存中刷新。但是,如果您在不使用参数的情况下动态生成SQL语句,则可能会遇到内存问题。
Dapper不包含数据库实现细节,它依赖于包括SQLite, SQL CE, Firebird, Oracle, MySQL, PostgreSQL和SQL Server的ADO.NET Provider。
Dapper只是简单的ORM框架,解决95%的应用问题,不试图解决所有问题
Dapper.Contrib - dapper的扩展
特性
Dapper.Contrib 包含插入、获取、更新和删除记录的方法。
以下是Dapper.Contrib包含的全部扩展方法清单:
T Get<T>(id); IEnumerable<T> GetAll<T>(); int Insert<T>(T obj); int Insert<T>(Enumerable<T> list); bool Update<T>(T obj); bool Update<T>(Enumerable<T> list); bool Delete<T>(T obj); bool Delete<T>(Enumerable<T> list); bool DeleteAll<T>();
如果实体模型存在Id属性,Dapper会默认Id属性为主键。
public class Car { public int Id { get; set; } // Works by convention public string Name { get; set; } }
如果实体模型不遵循包含Id属性,则必须在主键属性上加一个[Key]
或者 [ExplicitKey]特性。
public class User { [Key] int TheId { get; set; } string Name { get; set; } int Age { get; set; } }
[Key]
使用的时候,该属性必须是数据库主键并是自增。
[ExplicitKey]
只是在代码中作为主键,使用场景是数据库里面不是自增字段,在代码模型中作为主键使用。
Get
方法
根据Id获取实体数据
var car = connection.Get<Car>(1);
获取表中所有的实体数据
var cars = connection.GetAll<Car>();
Insert
方法
插入一个实体
connection.Insert(new Car { Name = "Volvo" });
插入多个实体
connection.Insert(cars);
更新一个实体
connection.Update(new Car() { Id = 1, Name = "Saab" });
更新多个实体
connection.Update(cars);
根据Id删除实体
connection.Delete(new Car() { Id = 1 });
删除多个实体
connection.Delete(cars);
删除全部实体
connection.DeleteAll<Car>();
其他特性
Dapper.Contrib 中的其他Atribute:
[Table("Tablename")]
- 当表名和实体类名不一样,可使用该Atribute知名数据库中的表名
[Table ("emps")] public class Employee { public int Id { get; set; } public string Name { get; set; } }
[Key]
- 当数据库中没有自增Id的时候,使用Key这个Atribute来指明自增和主键字段
public class Employee { [Key] public int EmployeeId { get; set; } public string Name { get; set; } }
[ExplicitKey]
- 当数据库中没有自增字段,使用该Atribute来指明主键字段
public class Employee { [ExplicitKey] public Guid EmployeeId { get; set; } public string Name { get; set; } }
-
[Write(true/false)]
- 该Atribute来指明是否写数据库true为写,false为不写 -
[Computed]
- 这个Atribute来指明不用更新数据库的字段限制与警告
-
SQLite
SQLiteConnection公开了一个与Dapper.Contrib提供的更新扩展冲突的更新事件。有两种方法可以解决这个问题。
1、从SqlMapperExtensions显式调用Update方法
-
SqlMapperExtensions.Update(_conn, new Employee { Id = 1, Name = "Mercedes" });
2、使用一个类型参数来保证方法签名的唯一
-
connection.Update<Car>(new Car() { Id = 1, Name = "Maruti" });