Commit c4905c57 authored by Johan Danforth's avatar Johan Danforth

New features in Dapper.Contrib

parent 24e55162
...@@ -44,7 +44,7 @@ public async Task TestSimpleGetAsync() ...@@ -44,7 +44,7 @@ public async Task TestSimpleGetAsync()
{ {
var id = await connection.InsertAsync(new User { Name = "Adama", Age = 10 }); var id = await connection.InsertAsync(new User { Name = "Adama", Age = 10 });
var user = await connection.GetAsync<User>(id); var user = await connection.GetAsync<User>(id);
user.Id.IsEqualTo((int)id); user.TheId.IsEqualTo((int)id);
user.Name.IsEqualTo("Adama"); user.Name.IsEqualTo("Adama");
await connection.DeleteAsync(user); await connection.DeleteAsync(user);
} }
...@@ -90,7 +90,7 @@ public async Task InsertCheckKeyAsync() ...@@ -90,7 +90,7 @@ public async Task InsertCheckKeyAsync()
(await connection.GetAsync<IUser>(3)).IsNull(); (await connection.GetAsync<IUser>(3)).IsNull();
User user = new User { Name = "Adamb", Age = 10 }; User user = new User { Name = "Adamb", Age = 10 };
int id = (int)await connection.InsertAsync(user); int id = (int)await connection.InsertAsync(user);
user.Id.IsEqualTo(id); user.TheId.IsEqualTo(id);
} }
} }
...@@ -102,9 +102,9 @@ public async Task BuilderSelectClauseAsync() ...@@ -102,9 +102,9 @@ public async Task BuilderSelectClauseAsync()
var data = new List<User>(); var data = new List<User>();
for (int i = 0; i < 100; i++) for (int i = 0; i < 100; i++)
{ {
var nU = new User { Age = rand.Next(70), Id = i, Name = Guid.NewGuid().ToString() }; var nU = new User { Age = rand.Next(70), TheId = i, Name = Guid.NewGuid().ToString() };
data.Add(nU); data.Add(nU);
nU.Id = (int)await connection.InsertAsync<User>(nU); nU.TheId = (int)await connection.InsertAsync<User>(nU);
} }
var builder = new SqlBuilder(); var builder = new SqlBuilder();
...@@ -118,8 +118,8 @@ public async Task BuilderSelectClauseAsync() ...@@ -118,8 +118,8 @@ public async Task BuilderSelectClauseAsync()
foreach (var u in data) foreach (var u in data)
{ {
if (!ids.Any(i => u.Id == i)) throw new Exception("Missing ids in select"); if (!ids.Any(i => u.TheId == i)) throw new Exception("Missing ids in select");
if (!users.Any(a => a.Id == u.Id && a.Name == u.Name && a.Age == u.Age)) throw new Exception("Missing users in select"); if (!users.Any(a => a.TheId == u.TheId && a.Name == u.Name && a.Age == u.Age)) throw new Exception("Missing users in select");
} }
} }
} }
......
...@@ -17,6 +17,14 @@ public static void IsEqualTo<T>(this T obj, T other) ...@@ -17,6 +17,14 @@ public static void IsEqualTo<T>(this T obj, T other)
} }
} }
public static void IsMoreThan(this int obj, int other)
{
if (obj < other)
{
throw new ApplicationException(string.Format("{0} should be larger than {1}", obj, other));
}
}
public static void IsSequenceEqualTo<T>(this IEnumerable<T> obj, IEnumerable<T> other) public static void IsSequenceEqualTo<T>(this IEnumerable<T> obj, IEnumerable<T> other)
{ {
if (!obj.SequenceEqual(other)) if (!obj.SequenceEqual(other))
......
using System.Data; using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlServerCe; using System.Data.SqlServerCe;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Transactions; using System.Transactions;
using Dapper.Contrib.Extensions; using Dapper.Contrib.Extensions;
using System.Collections.Generic;
using System;
namespace Dapper.Contrib.Tests namespace Dapper.Contrib.Tests
{ {
...@@ -90,10 +89,83 @@ public void TestSimpleGet() ...@@ -90,10 +89,83 @@ public void TestSimpleGet()
} }
} }
public void InsertList()
{
const int numberOfEntities = 100;
var users = new List<User>();
for (var i = 0; i < numberOfEntities; i++)
users.Add(new User { Name = "User " + i, Age = i });
using (var connection = GetOpenConnection())
{
connection.DeleteAll<User>();
var total = connection.Insert(users);
total.IsEqualTo(numberOfEntities);
users = connection.Query<User>("select * from users").ToList();
users.Count.IsEqualTo(numberOfEntities);
}
}
public void UpdateList()
{
const int numberOfEntities = 100;
var users = new List<User>();
for (var i = 0; i < numberOfEntities; i++)
users.Add(new User { Name = "User " + i, Age = i });
using (var connection = GetOpenConnection())
{
connection.DeleteAll<User>();
var total = connection.Insert(users);
total.IsEqualTo(numberOfEntities);
users = connection.Query<User>("select * from users").ToList();
users.Count.IsEqualTo(numberOfEntities);
foreach (var user in users)
{
user.Name = user.Name + " updated";
}
connection.Update(users);
var name = connection.Query<User>("select * from users").First().Name;
name.Contains("updated").IsTrue();
}
}
public void DeleteList()
{
const int numberOfEntities = 100;
var users = new List<User>();
for (var i = 0; i < numberOfEntities; i++)
users.Add(new User { Name = "User " + i, Age = i });
using (var connection = GetOpenConnection())
{
connection.DeleteAll<User>();
var total = connection.Insert(users);
total.IsEqualTo(numberOfEntities);
users = connection.Query<User>("select * from users").ToList();
users.Count.IsEqualTo(numberOfEntities);
var usersToDelete = users.Take(10).ToList();
connection.Delete(usersToDelete);
users = connection.Query<User>("select * from users").ToList();
users.Count.IsEqualTo(numberOfEntities - 10);
}
}
public void InsertGetUpdate() public void InsertGetUpdate()
{ {
using (var connection = GetOpenConnection()) using (var connection = GetOpenConnection())
{ {
connection.DeleteAll<User>();
connection.Get<User>(3).IsNull(); connection.Get<User>(3).IsNull();
//insert with computed attribute that should be ignored //insert with computed attribute that should be ignored
...@@ -123,9 +195,32 @@ public void InsertGetUpdate() ...@@ -123,9 +195,32 @@ public void InsertGetUpdate()
connection.Query<User>("select * from Users").Count().IsEqualTo(0); connection.Query<User>("select * from Users").Count().IsEqualTo(0);
connection.Update(notrackedUser).IsEqualTo(false); //returns false, user not found connection.Update(notrackedUser).IsEqualTo(false); //returns false, user not found
} }
} }
public void GetAll()
{
const int numberOfEntities = 100;
var users = new List<User>();
for (var i = 0; i < numberOfEntities; i++)
users.Add(new User { Name = "User " + i, Age = i });
using (var connection = GetOpenConnection())
{
connection.DeleteAll<User>();
var total = connection.Insert(users);
total.IsEqualTo(numberOfEntities);
users = connection.GetAll<User>().ToList();
users.Count.IsEqualTo(numberOfEntities);
var iusers = connection.GetAll<IUser>().ToList();
iusers.Count.IsEqualTo(numberOfEntities);
}
}
public void Transactions() public void Transactions()
{ {
using (var connection = GetOpenConnection()) using (var connection = GetOpenConnection())
...@@ -182,7 +277,7 @@ public void BuilderSelectClause() ...@@ -182,7 +277,7 @@ public void BuilderSelectClause()
{ {
var nU = new User { Age = rand.Next(70), Id = i, Name = Guid.NewGuid().ToString() }; var nU = new User { Age = rand.Next(70), Id = i, Name = Guid.NewGuid().ToString() };
data.Add(nU); data.Add(nU);
nU.Id = (int)connection.Insert<User>(nU); nU.Id = (int)connection.Insert(nU);
} }
var builder = new SqlBuilder(); var builder = new SqlBuilder();
...@@ -212,6 +307,7 @@ public void BuilderTemplateWOComposition() ...@@ -212,6 +307,7 @@ public void BuilderTemplateWOComposition()
using (var connection = GetOpenConnection()) using (var connection = GetOpenConnection())
{ {
connection.DeleteAll<User>();
connection.Insert(new User { Age = 5, Name = "Testy McTestington" }); connection.Insert(new User { Age = 5, Name = "Testy McTestington" });
if (connection.Query<int>(template.RawSql, template.Parameters).Single() != 1) if (connection.Query<int>(template.RawSql, template.Parameters).Single() != 1)
...@@ -221,11 +317,12 @@ public void BuilderTemplateWOComposition() ...@@ -221,11 +317,12 @@ public void BuilderTemplateWOComposition()
public void InsertFieldWithReservedName() public void InsertFieldWithReservedName()
{ {
using (var conneciton = GetOpenConnection()) using (var connection = GetOpenConnection())
{ {
var id = conneciton.Insert(new Result() { Name = "Adam", Order = 1 }); connection.DeleteAll<User>();
var id = connection.Insert(new Result() { Name = "Adam", Order = 1 });
var result = conneciton.Get<Result>(id); var result = connection.Get<Result>(id);
result.Order.IsEqualTo(1); result.Order.IsEqualTo(1);
} }
......
...@@ -59,6 +59,9 @@ ...@@ -59,6 +59,9 @@
<Name>Dapper NET40</Name> <Name>Dapper NET40</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="Readme.md" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
......
...@@ -5,58 +5,87 @@ Features ...@@ -5,58 +5,87 @@ Features
-------- --------
Dapper.Contrib contains a number of helper methods for inserting, getting, updating and deleting files. Dapper.Contrib contains a number of helper methods for inserting, getting, updating and deleting files.
The object you are working with must have a property named Id or a property marked with a [Key] attribute. As with dapper, As with dapper, all extension methods assume the connection is already open, they will fail if the
all extension methods assume the connection is already open, they will fail if the connection is closed. connection is closed. The full list of extension methods in Dapper.Contrib right now are:
Inserts
-------
```csharp ```csharp
public static long Insert<T>(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) 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>();
``` ```
For these extensions to work, the entity in question _MUST_ have a key-property, a property named "id" or decorated with
a [Key] attribute.
```csharp ```csharp
public class Car public class Car
{ {
public int Id { get; set; } public int Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
} }
connection.Insert(new Car { Name = "Volvo" }); public class User
{
[Key]
int TheId { get; set; }
string Name { get; set; }
int Age { get; set; }
}
``` ```
Gets Gets
------- -------
```csharp Get one specific entity based on id, or a list of all entities in the table.
public static T Get<T>(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null)
```
```csharp ```csharp
var car = connection.Get<Car>(1); var car = connection.Get<Car>(1);
var cars = connection.GetAll<Car>();
``` ```
Updates Inserts
------- -------
Insert one entity or a list of entities.
```csharp ```csharp
public static bool Update<T>(this IDbConnection connection, T entityToUpdate, IDbTransaction transaction = null, int? commandTimeout = null) connection.Insert(new Car { Name = "Volvo" });
connection.Insert(cars);
``` ```
Updates
-------
Update one specific entity or update a list of entities.
```csharp ```csharp
connection.Update(new Car() { Id = 1, Name = "Saab" }); connection.Update(new Car() { Id = 1, Name = "Saab" });
connection.Update(cars);
``` ```
Deletes Deletes
------- -------
```csharp Delete one specific entity, a list of entities, or _ALL_ entities in the table.
public static bool Delete<T>(this IDbConnection connection, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null)
public static bool DeleteAll<T>(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null)
```
```csharp ```csharp
connection.Delete(new Car() { Id = 1 }); connection.Delete(new Car() { Id = 1 });
connection.Delete(cars);
connection.DeleteAll<Car>(); connection.DeleteAll<Car>();
``` ```
Attributes Special Attributes
---------- ----------
Dapper.Contrib makes use of some optional attributes: Dapper.Contrib makes use of some optional attributes:
......
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Linq; using System.Linq;
...@@ -109,6 +110,7 @@ private static bool IsWriteable(PropertyInfo pi) ...@@ -109,6 +110,7 @@ private static bool IsWriteable(PropertyInfo pi)
public static T Get<T>(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class public static T Get<T>(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{ {
var type = typeof(T); var type = typeof(T);
string sql; string sql;
if (!GetQueries.TryGetValue(type.TypeHandle, out sql)) if (!GetQueries.TryGetValue(type.TypeHandle, out sql))
{ {
...@@ -122,7 +124,6 @@ private static bool IsWriteable(PropertyInfo pi) ...@@ -122,7 +124,6 @@ private static bool IsWriteable(PropertyInfo pi)
var name = GetTableName(type); var name = GetTableName(type);
// TODO: pluralizer
// TODO: query information schema and only select fields that are both in information schema and underlying class / interface // TODO: query information schema and only select fields that are both in information schema and underlying class / interface
sql = "select * from " + name + " where " + onlyKey.Name + " = @id"; sql = "select * from " + name + " where " + onlyKey.Name + " = @id";
GetQueries[type.TypeHandle] = sql; GetQueries[type.TypeHandle] = sql;
...@@ -156,6 +157,58 @@ private static bool IsWriteable(PropertyInfo pi) ...@@ -156,6 +157,58 @@ private static bool IsWriteable(PropertyInfo pi)
} }
return obj; return obj;
} }
/// <summary>
/// Returns a list of entites from table "Ts".
/// Id of T must be marked with [Key] attribute.
/// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension
/// for optimal performance.
/// </summary>
/// <typeparam name="T">Interface or type to create and populate</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <returns>Entity of T</returns>
public static IEnumerable<T> GetAll<T>(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
var cacheType = typeof (List<T>);
string sql;
if (!GetQueries.TryGetValue(cacheType.TypeHandle, out sql))
{
var keys = KeyPropertiesCache(type);
if (keys.Count() > 1)
throw new DataException("Get<T> only supports an entity with a single [Key] property");
if (!keys.Any())
throw new DataException("Get<T> only supports en entity with a [Key] property");
var onlyKey = keys.First();
var name = GetTableName(type);
// TODO: query information schema and only select fields that are both in information schema and underlying class / interface
sql = "select * from " + name ;
GetQueries[cacheType.TypeHandle] = sql;
}
if (!type.IsInterface) return connection.Query<T>(sql, null, transaction, commandTimeout: commandTimeout);
var result = connection.Query(sql);
var list = new List<T>();
foreach (IDictionary<string, object> res in result)
{
var obj = ProxyGenerator.GetInterfaceProxy<T>();
foreach (var property in TypePropertiesCache(type))
{
var val = res[property.Name];
property.SetValue(obj, val, null);
}
((IProxy)obj).IsDirty = false; //reset change tracking and return
list.Add(obj);
}
return list;
}
private static string GetTableName(Type type) private static string GetTableName(Type type)
{ {
string name; string name;
...@@ -176,14 +229,23 @@ private static string GetTableName(Type type) ...@@ -176,14 +229,23 @@ private static string GetTableName(Type type)
} }
/// <summary> /// <summary>
/// Inserts an entity into table "Ts" and returns identity id. /// Inserts an entity into table "Ts" and returns identity id or number if inserted rows if inserting a list.
/// </summary> /// </summary>
/// <param name="connection">Open SqlConnection</param> /// <param name="connection">Open SqlConnection</param>
/// <param name="entityToInsert">Entity to insert</param> /// <param name="entityToInsert">Entity to insert, can be list of entities</param>
/// <returns>Identity of inserted entity</returns> /// <returns>Identity of inserted entity, or number of inserted rows if inserting a list</returns>
public static long Insert<T>(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class public static long Insert<T>(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{ {
var isList = false;
var type = typeof(T); var type = typeof(T);
if (type.IsArray || type.IsGenericType)
{
isList = true;
type = type.GetGenericArguments()[0];
}
var name = GetTableName(type); var name = GetTableName(type);
var sbColumnList = new StringBuilder(null); var sbColumnList = new StringBuilder(null);
var allProperties = TypePropertiesCache(type); var allProperties = TypePropertiesCache(type);
...@@ -207,9 +269,17 @@ private static string GetTableName(Type type) ...@@ -207,9 +269,17 @@ private static string GetTableName(Type type)
if (i < allPropertiesExceptKeyAndComputed.Count() - 1) if (i < allPropertiesExceptKeyAndComputed.Count() - 1)
sbParameterList.Append(", "); sbParameterList.Append(", ");
} }
var adapter = GetFormatter(connection);
return adapter.Insert(connection, transaction, commandTimeout, name, sbColumnList.ToString(), if (!isList) //single entity
sbParameterList.ToString(), keyProperties, entityToInsert); {
var adapter = GetFormatter(connection);
return adapter.Insert(connection, transaction, commandTimeout, name, sbColumnList.ToString(),
sbParameterList.ToString(), keyProperties, entityToInsert);
}
//insert list of entities
var cmd = String.Format("insert into {0} ({1}) values ({2})", name, sbColumnList, sbParameterList);
return connection.Execute(cmd, entityToInsert, transaction, commandTimeout);
} }
/// <summary> /// <summary>
...@@ -229,6 +299,9 @@ private static string GetTableName(Type type) ...@@ -229,6 +299,9 @@ private static string GetTableName(Type type)
var type = typeof(T); var type = typeof(T);
if (type.IsArray || type.IsGenericType)
type = type.GetGenericArguments()[0];
var keyProperties = KeyPropertiesCache(type); var keyProperties = KeyPropertiesCache(type);
if (!keyProperties.Any()) if (!keyProperties.Any())
throw new ArgumentException("Entity must have at least one [Key] property"); throw new ArgumentException("Entity must have at least one [Key] property");
...@@ -275,6 +348,9 @@ private static string GetTableName(Type type) ...@@ -275,6 +348,9 @@ private static string GetTableName(Type type)
var type = typeof(T); var type = typeof(T);
if (type.IsArray || type.IsGenericType)
type = type.GetGenericArguments()[0];
var keyProperties = KeyPropertiesCache(type); var keyProperties = KeyPropertiesCache(type);
if (!keyProperties.Any()) if (!keyProperties.Any())
throw new ArgumentException("Entity must have at least one [Key] property"); throw new ArgumentException("Entity must have at least one [Key] property");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment