Commit a67ed727 authored by johandanforth's avatar johandanforth

Cleaned up shared files in Contrib and tests. Also added tests for transactions in Contrib

parent c8bd264d
...@@ -44,7 +44,10 @@ ...@@ -44,7 +44,10 @@
<Compile Include="..\Dapper.Contrib\Properties\AssemblyInfo.cs"> <Compile Include="..\Dapper.Contrib\Properties\AssemblyInfo.cs">
<Link>AssemblyInfo.cs</Link> <Link>AssemblyInfo.cs</Link>
</Compile> </Compile>
<Compile Include="SqlMapperExtensions.cs" /> <Compile Include="..\Dapper.Contrib\SqlMapperExtensions.cs">
<Link>SqlMapperExtensions.cs</Link>
</Compile>
<Compile Include="SqlMapperExtensionsAsync.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Properties\" /> <Folder Include="Properties\" />
......
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Dapper;
#pragma warning disable 1573, 1591 // xml comments
namespace Dapper.Contrib.Extensions
{
public static partial class SqlMapperExtensions
{
/// <summary>
/// Returns a single entity by a single id from table "Ts" asynchronously using .NET 4.5 Task. T must be of interface type.
/// Id must be marked with [Key] attribute.
/// Created entity is tracked/intercepted for changes and used by the Update() extension.
/// </summary>
/// <typeparam name="T">Interface type to create and populate</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="id">Id of the entity to get, must be marked with [Key] attribute</param>
/// <returns>Entity of T</returns>
public static async Task<T> GetAsync<T>(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
string sql;
if (!GetQueries.TryGetValue(type.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: pluralizer
// 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";
GetQueries[type.TypeHandle] = sql;
}
var dynParms = new DynamicParameters();
dynParms.Add("@id", id);
T obj;
if (type.IsInterface)
{
var res = (await connection.QueryAsync<dynamic>(sql, dynParms).ConfigureAwait(false)).FirstOrDefault() as IDictionary<string, object>;
if (res == null)
return null;
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
}
else
{
obj = (await connection.QueryAsync<T>(sql, dynParms, transaction, commandTimeout).ConfigureAwait(false)).FirstOrDefault();
}
return obj;
}
/// <summary>
/// Inserts an entity into table "Ts" asynchronously using .NET 4.5 Task and returns identity id.
/// </summary>
/// <param name="connection">Open SqlConnection</param>
/// <param name="entityToInsert">Entity to insert</param>
/// <returns>Identity of inserted entity</returns>
public static Task<int> InsertAsync<T>(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
var name = GetTableName(type);
var sbColumnList = new StringBuilder(null);
var allProperties = TypePropertiesCache(type);
var keyProperties = KeyPropertiesCache(type);
var computedProperties = ComputedPropertiesCache(type);
var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count(); i++)
{
var property = allPropertiesExceptKeyAndComputed.ElementAt(i);
sbColumnList.AppendFormat("[{0}]", property.Name);
if (i < allPropertiesExceptKeyAndComputed.Count() - 1)
sbColumnList.Append(", ");
}
var sbParameterList = new StringBuilder(null);
for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count(); i++)
{
var property = allPropertiesExceptKeyAndComputed.ElementAt(i);
sbParameterList.AppendFormat("@{0}", property.Name);
if (i < allPropertiesExceptKeyAndComputed.Count() - 1)
sbParameterList.Append(", ");
}
var adapter = GetFormatter(connection);
return adapter.InsertAsync(connection, transaction, commandTimeout, name, sbColumnList.ToString(),
sbParameterList.ToString(), keyProperties, entityToInsert);
}
/// <summary>
/// Updates entity in table "Ts" asynchronously using .NET 4.5 Task, checks if the entity is modified if the entity is tracked by the Get() extension.
/// </summary>
/// <typeparam name="T">Type to be updated</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="entityToUpdate">Entity to be updated</param>
/// <returns>true if updated, false if not found or not modified (tracked entities)</returns>
public static async Task<bool> UpdateAsync<T>(this IDbConnection connection, T entityToUpdate, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var proxy = entityToUpdate as IProxy;
if (proxy != null)
{
if (!proxy.IsDirty) return false;
}
var type = typeof(T);
var keyProperties = KeyPropertiesCache(type);
if (!keyProperties.Any())
throw new ArgumentException("Entity must have at least one [Key] property");
var name = GetTableName(type);
var sb = new StringBuilder();
sb.AppendFormat("update {0} set ", name);
var allProperties = TypePropertiesCache(type);
var computedProperties = ComputedPropertiesCache(type);
var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
for (var i = 0; i < nonIdProps.Count(); i++)
{
var property = nonIdProps.ElementAt(i);
sb.AppendFormat("{0} = @{1}", property.Name, property.Name);
if (i < nonIdProps.Count() - 1)
sb.AppendFormat(", ");
}
sb.Append(" where ");
for (var i = 0; i < keyProperties.Count(); i++)
{
var property = keyProperties.ElementAt(i);
sb.AppendFormat("{0} = @{1}", property.Name, property.Name);
if (i < keyProperties.Count() - 1)
sb.AppendFormat(" and ");
}
var updated = await connection.ExecuteAsync(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction).ConfigureAwait(false);
return updated > 0;
}
/// <summary>
/// Delete entity in table "Ts" asynchronously using .NET 4.5 Task.
/// </summary>
/// <typeparam name="T">Type of entity</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <param name="entityToDelete">Entity to delete</param>
/// <returns>true if deleted, false if not found</returns>
public static async Task<bool> DeleteAsync<T>(this IDbConnection connection, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
if (entityToDelete == null)
throw new ArgumentException("Cannot Delete null Object", "entityToDelete");
var type = typeof(T);
var keyProperties = KeyPropertiesCache(type);
if (!keyProperties.Any())
throw new ArgumentException("Entity must have at least one [Key] property");
var name = GetTableName(type);
var sb = new StringBuilder();
sb.AppendFormat("delete from {0} where ", name);
for (var i = 0; i < keyProperties.Count(); i++)
{
var property = keyProperties.ElementAt(i);
sb.AppendFormat("{0} = @{1}", property.Name, property.Name);
if (i < keyProperties.Count() - 1)
sb.AppendFormat(" and ");
}
var deleted = await connection.ExecuteAsync(sb.ToString(), entityToDelete, transaction, commandTimeout).ConfigureAwait(false);
return deleted > 0;
}
/// <summary>
/// Delete all entities in the table related to the type T asynchronously using .NET 4.5 Task.
/// </summary>
/// <typeparam name="T">Type of entity</typeparam>
/// <param name="connection">Open SqlConnection</param>
/// <returns>true if deleted, false if none found</returns>
public static async Task<bool> DeleteAllAsync<T>(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
var name = GetTableName(type);
var statement = String.Format("delete from {0}", name);
var deleted = await connection.ExecuteAsync(statement, null, transaction, commandTimeout).ConfigureAwait(false);
return deleted > 0;
}
}
}
public partial interface ISqlAdapter
{
Task<int> InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert);
}
public partial class SqlServerAdapter
{
public async Task<int> InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert)
{
var cmd = String.Format("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList);
await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false);
//NOTE: would prefer to use IDENT_CURRENT('tablename') or IDENT_SCOPE but these are not available on SQLCE
var r = await connection.QueryAsync<dynamic>("select @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false);
var id = (int)r.First().id;
var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (propertyInfos.Any())
propertyInfos.First().SetValue(entityToInsert, id, null);
return id;
}
}
public partial class PostgresAdapter
{
public async Task<int> InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert)
{
var sb = new StringBuilder();
sb.AppendFormat("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList);
// If no primary key then safe to assume a join table with not too much data to return
var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (!propertyInfos.Any())
sb.Append(" RETURNING *");
else
{
sb.Append(" RETURNING ");
bool first = true;
foreach (var property in propertyInfos)
{
if (!first)
sb.Append(", ");
first = false;
sb.Append(property.Name);
}
}
var results = await connection.QueryAsync<dynamic>(sb.ToString(), entityToInsert, transaction, commandTimeout).ConfigureAwait(false);
// Return the key by assinging the corresponding property in the object - by product is that it supports compound primary keys
var id = 0;
foreach (var p in propertyInfos)
{
var value = ((IDictionary<string, object>)results.First())[p.Name.ToLower()];
p.SetValue(entityToInsert, value, null);
if (id == 0)
id = Convert.ToInt32(value);
}
return id;
}
}
public partial class SQLiteAdapter
{
public async Task<int> InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert)
{
var cmd = String.Format("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList);
await connection.ExecuteAsync(cmd, entityToInsert, transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false);
var r = await connection.QueryAsync<dynamic>("select last_insert_rowid() id", transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false);
var id = (int)r.First().id;
var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (propertyInfos.Any())
propertyInfos.First().SetValue(entityToInsert, id, null);
return id;
}
}
using System;
using System.Collections.Generic;
using System.Linq;
namespace Dapper.Contrib.Tests_NET45
{
/// <summary>
/// Assert extensions borrowed from Sam's code in DapperTests
/// </summary>
static class Assert
{
public static void IsEqualTo<T>(this T obj, T other)
{
if (!obj.Equals(other))
{
throw new ApplicationException(string.Format("{0} should be equals to {1}", obj, other));
}
}
public static void IsSequenceEqualTo<T>(this IEnumerable<T> obj, IEnumerable<T> other)
{
if (!obj.SequenceEqual(other))
{
throw new ApplicationException(string.Format("{0} should be equals to {1}", obj, other));
}
}
public static void IsFalse(this bool b)
{
if (b)
{
throw new ApplicationException("Expected false");
}
}
public static void IsTrue(this bool b)
{
if (!b)
{
throw new ApplicationException("Expected true");
}
}
public static void IsNull(this object obj)
{
if (obj != null)
{
throw new ApplicationException("Expected null");
}
}
}
}
\ No newline at end of file
...@@ -7,8 +7,8 @@ ...@@ -7,8 +7,8 @@
<ProjectGuid>{7A85178F-4ADC-4E4C-BF08-17FC99488A9A}</ProjectGuid> <ProjectGuid>{7A85178F-4ADC-4E4C-BF08-17FC99488A9A}</ProjectGuid>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Dapper.Contrib.Tests_NET45</RootNamespace> <RootNamespace>Dapper.Contrib.Tests</RootNamespace>
<AssemblyName>Dapper.Contrib.Tests NET45</AssemblyName> <AssemblyName>Dapper.Contrib.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
</PropertyGroup> </PropertyGroup>
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL" /> <Reference Include="System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
...@@ -42,10 +43,14 @@ ...@@ -42,10 +43,14 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Assert.cs" /> <Compile Include="..\Dapper.Contrib.Tests\Assert.cs">
<Link>Assert.cs</Link>
</Compile>
<Compile Include="..\Dapper.Contrib.Tests\Tests.cs">
<Link>Tests.cs</Link>
</Compile>
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Tests.cs" />
<Compile Include="TestsAsync.cs" /> <Compile Include="TestsAsync.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
......
using System; using System;
using System.Collections.Generic;
using System.Data.SqlServerCe; using System.Data.SqlServerCe;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Dapper.Contrib.Tests_NET45 namespace Dapper.Contrib.Tests
{ {
class Program class Program
{ {
...@@ -47,50 +43,21 @@ private static void RunTests() ...@@ -47,50 +43,21 @@ private static void RunTests()
var tester = new Tests(); var tester = new Tests();
foreach (var method in typeof(Tests).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) foreach (var method in typeof(Tests).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{ {
if (method.ReturnType != typeof(Task)) Console.Write("Running " + method.Name);
{ method.Invoke(tester, null);
Console.Write("Running " + method.Name); Console.WriteLine(" - OK!");
method.Invoke(tester, null);
Console.WriteLine(" - OK!");
}
} }
} }
private static void RunAsyncTests() private static void RunAsyncTests()
{ {
var tester = new TestsAsync(); var tester = new TestsAsync();
foreach (var method in typeof(TestsAsync).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
Console.Write("Running TableNameAsync"); {
Task.WaitAll(tester.TableNameAsync()); Console.Write("Running " + method.Name);
Console.WriteLine(" - OK!"); Task.WaitAll((Task)method.Invoke(tester, null));
Console.WriteLine(" - OK!");
Console.Write("Running TestSimpleGetAsync"); }
Task.WaitAll(tester.TestSimpleGetAsync());
Console.WriteLine(" - OK!");
Console.Write("Running InsertGetUpdateAsync");
Task.WaitAll(tester.InsertGetUpdateAsync());
Console.WriteLine(" - OK!");
Console.Write("Running InsertCheckKeyAsync");
Task.WaitAll(tester.InsertCheckKeyAsync());
Console.WriteLine(" - OK!");
Console.Write("Running BuilderSelectClauseAsync");
Task.WaitAll(tester.BuilderSelectClauseAsync());
Console.WriteLine(" - OK!");
Console.Write("Running BuilderTemplateWOCompositionAsync");
Task.WaitAll(tester.BuilderTemplateWOCompositionAsync());
Console.WriteLine(" - OK!");
Console.Write("Running InsertFieldWithReservedNameAsync");
Task.WaitAll(tester.InsertFieldWithReservedNameAsync());
Console.WriteLine(" - OK!");
Console.Write("Running DeleteAllAsync");
Task.WaitAll(tester.DeleteAllAsync());
Console.WriteLine(" - OK!");
} }
} }
} }
using System.Data;
using System.Data.SqlServerCe;
using System.IO;
using System.Linq;
using System.Reflection;
using Dapper.Contrib.Extensions;
using System.Collections.Generic;
using System;
using Dapper;
namespace Dapper.Contrib.Tests_NET45
{
public interface IUser
{
[Key]
int Id { get; set; }
string Name { get; set; }
int Age { get; set; }
}
public class User : IUser
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
[Table("Automobiles")]
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
}
[Table("Results")]
public class Result
{
public int Id { get; set; }
public string Name { get; set; }
public int Order { get; set; }
}
public class Tests
{
private IDbConnection GetOpenConnection()
{
var projLoc = Assembly.GetAssembly(GetType()).Location;
var projFolder = Path.GetDirectoryName(projLoc);
var connection = new SqlCeConnection("Data Source = " + projFolder + "\\Test.sdf;");
connection.Open();
return connection;
}
public void TableName()
{
using (var connection = GetOpenConnection())
{
// tests against "Automobiles" table (Table attribute)
connection.Insert(new Car {Name = "Volvo"});
connection.Get<Car>(1).Name.IsEqualTo("Volvo");
connection.Update(new Car() {Id = 1, Name = "Saab"}).IsEqualTo(true);
connection.Get<Car>(1).Name.IsEqualTo("Saab");
connection.Delete(new Car() {Id = 1}).IsEqualTo(true);
connection.Get<Car>(1).IsNull();
}
}
public void TestSimpleGet()
{
using (var connection = GetOpenConnection())
{
var id = connection.Insert(new User { Name = "Adama", Age = 10 });
var user = connection.Get<User>(id);
user.Id.IsEqualTo((int)id);
user.Name.IsEqualTo("Adama");
connection.Delete(user);
}
}
public void InsertGetUpdate()
{
using (var connection = GetOpenConnection())
{
connection.Get<User>(3).IsNull();
var id = connection.Insert(new User {Name = "Adam", Age = 10});
//get a user with "isdirty" tracking
var user = connection.Get<IUser>(id);
user.Name.IsEqualTo("Adam");
connection.Update(user).IsEqualTo(false); //returns false if not updated, based on tracking
user.Name = "Bob";
connection.Update(user).IsEqualTo(true); //returns true if updated, based on tracking
user = connection.Get<IUser>(id);
user.Name.IsEqualTo("Bob");
//get a user with no tracking
var notrackedUser = connection.Get<User>(id);
notrackedUser.Name.IsEqualTo("Bob");
connection.Update(notrackedUser).IsEqualTo(true); //returns true, even though user was not changed
notrackedUser.Name = "Cecil";
connection.Update(notrackedUser).IsEqualTo(true);
connection.Get<User>(id).Name.IsEqualTo("Cecil");
connection.Query<User>("select * from Users").Count().IsEqualTo(1);
connection.Delete(user).IsEqualTo(true);
connection.Query<User>("select * from Users").Count().IsEqualTo(0);
connection.Update(notrackedUser).IsEqualTo(false); //returns false, user not found
}
}
public void InsertCheckKey()
{
using (var connection = GetOpenConnection())
{
connection.Get<IUser>(3).IsNull();
User user = new User { Name = "Adamb", Age = 10 };
int id = (int)connection.Insert(user);
user.Id.IsEqualTo(id);
}
}
public void BuilderSelectClause()
{
using (var connection = GetOpenConnection())
{
var rand = new Random(8675309);
var data = new List<User>();
for (int i = 0; i < 100; i++)
{
var nU = new User { Age = rand.Next(70), Id = i, Name = Guid.NewGuid().ToString() };
data.Add(nU);
nU.Id = (int)connection.Insert<User>(nU);
}
var builder = new SqlBuilder();
var justId = builder.AddTemplate("SELECT /**select**/ FROM Users");
var all = builder.AddTemplate("SELECT Name, /**select**/, Age FROM Users");
builder.Select("Id");
var ids = connection.Query<int>(justId.RawSql, justId.Parameters);
var users = connection.Query<User>(all.RawSql, all.Parameters);
foreach (var u in data)
{
if (!ids.Any(i => u.Id == 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");
}
}
}
public void BuilderTemplateWOComposition()
{
var builder = new SqlBuilder();
var template = builder.AddTemplate("SELECT COUNT(*) FROM Users WHERE Age = @age", new {age = 5});
if (template.RawSql == null) throw new Exception("RawSql null");
if (template.Parameters == null) throw new Exception("Parameters null");
using (var connection = GetOpenConnection())
{
connection.Insert(new User { Age = 5, Name = "Testy McTestington" });
if (connection.Query<int>(template.RawSql, template.Parameters).Single() != 1)
throw new Exception("Query failed");
}
}
public void InsertFieldWithReservedName()
{
using (var conneciton = GetOpenConnection())
{
var id = conneciton.Insert(new Result() { Name = "Adam", Order = 1 });
var result = conneciton.Get<Result>(id);
result.Order.IsEqualTo(1);
}
}
public void DeleteAll()
{
using (var connection = GetOpenConnection())
{
var id1 = connection.Insert(new User() { Name = "Alice", Age = 32 });
var id2 = connection.Insert(new User() { Name = "Bob", Age = 33 });
connection.DeleteAll<User>();
connection.Get<User>(id1).IsNull();
connection.Get<User>(id2).IsNull();
}
}
}
}
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Dapper.Contrib.Tests_NET45 namespace Dapper.Contrib.Tests
{ {
public class TestsAsync public class TestsAsync
{ {
......
...@@ -44,10 +44,8 @@ ...@@ -44,10 +44,8 @@
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL"> <Reference Include="System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL" />
<SpecificVersion>False</SpecificVersion> <Reference Include="System.Transactions" />
<HintPath>Dependencies\System.Data.SqlServerCe.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
......
using System; using System;
using System.Collections.Generic;
using System.Data.SqlServerCe; using System.Data.SqlServerCe;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
namespace Dapper.Contrib.Tests namespace Dapper.Contrib.Tests
{ {
...@@ -15,6 +11,8 @@ static void Main(string[] args) ...@@ -15,6 +11,8 @@ static void Main(string[] args)
{ {
Setup(); Setup();
RunTests(); RunTests();
Console.WriteLine("Press any key...");
Console.ReadKey();
} }
private static void Setup() private static void Setup()
...@@ -46,9 +44,6 @@ private static void RunTests() ...@@ -46,9 +44,6 @@ private static void RunTests()
method.Invoke(tester, null); method.Invoke(tester, null);
Console.WriteLine(" - OK!"); Console.WriteLine(" - OK!");
} }
Console.WriteLine("(end of tests; press any key)");
Console.ReadKey();
} }
} }
......
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Transactions;
using Dapper.Contrib.Extensions; using Dapper.Contrib.Extensions;
using System.Collections.Generic; using System.Collections.Generic;
using System; using System;
using Dapper;
namespace Dapper.Contrib.Tests namespace Dapper.Contrib.Tests
...@@ -31,6 +31,8 @@ public class Car ...@@ -31,6 +31,8 @@ public class Car
{ {
public int Id { get; set; } public int Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
[Computed]
public string Computed { get; set; }
} }
[Table("Results")] [Table("Results")]
...@@ -53,16 +55,25 @@ private IDbConnection GetOpenConnection() ...@@ -53,16 +55,25 @@ private IDbConnection GetOpenConnection()
return connection; return connection;
} }
private IDbConnection GetConnection()
{
var projLoc = Assembly.GetAssembly(GetType()).Location;
var projFolder = Path.GetDirectoryName(projLoc);
var connection = new SqlCeConnection("Data Source = " + projFolder + "\\Test.sdf;");
return connection;
}
public void TableName() public void TableName()
{ {
using (var connection = GetOpenConnection()) using (var connection = GetOpenConnection())
{ {
// tests against "Automobiles" table (Table attribute) // tests against "Automobiles" table (Table attribute)
connection.Insert(new Car {Name = "Volvo"}); connection.Insert(new Car { Name = "Volvo" }).IsEqualTo(1);
connection.Get<Car>(1).Name.IsEqualTo("Volvo"); connection.Get<Car>(1).Name.IsEqualTo("Volvo");
connection.Update(new Car() {Id = 1, Name = "Saab"}).IsEqualTo(true); connection.Update(new Car() { Id = 1, Name = "Saab" }).IsEqualTo(true);
connection.Get<Car>(1).Name.IsEqualTo("Saab"); connection.Get<Car>(1).Name.IsEqualTo("Saab");
connection.Delete(new Car() {Id = 1}).IsEqualTo(true); connection.Delete(new Car() { Id = 1 }).IsEqualTo(true);
connection.Get<Car>(1).IsNull(); connection.Get<Car>(1).IsNull();
} }
} }
...@@ -85,7 +96,10 @@ public void InsertGetUpdate() ...@@ -85,7 +96,10 @@ public void InsertGetUpdate()
{ {
connection.Get<User>(3).IsNull(); connection.Get<User>(3).IsNull();
var id = connection.Insert(new User {Name = "Adam", Age = 10}); //insert with computed attribute that should be ignored
connection.Insert(new Car { Name = "Volvo", Computed = "this property should be ignored" });
var id = connection.Insert(new User { Name = "Adam", Age = 10 });
//get a user with "isdirty" tracking //get a user with "isdirty" tracking
var user = connection.Get<IUser>(id); var user = connection.Get<IUser>(id);
...@@ -112,6 +126,41 @@ public void InsertGetUpdate() ...@@ -112,6 +126,41 @@ public void InsertGetUpdate()
} }
} }
public void Transactions()
{
using (var connection = GetOpenConnection())
{
var id = connection.Insert(new Car { Name = "one car" }); //insert outside transaction
var tran = connection.BeginTransaction();
var car = connection.Get<Car>(id, tran);
var orgName = car.Name;
car.Name = "Another car";
connection.Update(car, tran);
tran.Rollback();
car = connection.Get<Car>(id); //updates should have been rolled back
car.Name.IsEqualTo(orgName);
}
}
public void TransactionScope()
{
using (var connection = GetConnection())
{
using (var txscope = new TransactionScope())
{
connection.Open(); //connection MUST be opened inside the transactionscope
var id = connection.Insert(new Car { Name = "one car" }); //inser car within transaction
txscope.Dispose(); //rollback
connection.Get<Car>(id).IsNull(); //returns null - car with that id should not exist
}
}
}
public void InsertCheckKey() public void InsertCheckKey()
{ {
using (var connection = GetOpenConnection()) using (var connection = GetOpenConnection())
...@@ -156,7 +205,7 @@ public void BuilderSelectClause() ...@@ -156,7 +205,7 @@ public void BuilderSelectClause()
public void BuilderTemplateWOComposition() public void BuilderTemplateWOComposition()
{ {
var builder = new SqlBuilder(); var builder = new SqlBuilder();
var template = builder.AddTemplate("SELECT COUNT(*) FROM Users WHERE Age = @age", new {age = 5}); var template = builder.AddTemplate("SELECT COUNT(*) FROM Users WHERE Age = @age", new { age = 5 });
if (template.RawSql == null) throw new Exception("RawSql null"); if (template.RawSql == null) throw new Exception("RawSql null");
if (template.Parameters == null) throw new Exception("Parameters null"); if (template.Parameters == null) throw new Exception("Parameters null");
...@@ -188,11 +237,11 @@ public void DeleteAll() ...@@ -188,11 +237,11 @@ public void DeleteAll()
{ {
var id1 = connection.Insert(new User() { Name = "Alice", Age = 32 }); var id1 = connection.Insert(new User() { Name = "Alice", Age = 32 });
var id2 = connection.Insert(new User() { Name = "Bob", Age = 33 }); var id2 = connection.Insert(new User() { Name = "Bob", Age = 33 });
connection.DeleteAll<User>(); connection.DeleteAll<User>().IsTrue();
connection.Get<User>(id1).IsNull(); connection.Get<User>(id1).IsNull();
connection.Get<User>(id2).IsNull(); connection.Get<User>(id2).IsNull();
} }
} }
} }
} }
\ No newline at end of file
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Reflection.Emit; using System.Reflection.Emit;
using System.Threading; using System.Threading;
using System.Runtime.CompilerServices;
using Dapper; using Dapper;
#pragma warning disable 1573, 1591 // xml comments #pragma warning disable 1573, 1591 // xml comments
...@@ -16,44 +15,47 @@ ...@@ -16,44 +15,47 @@
namespace Dapper.Contrib.Extensions namespace Dapper.Contrib.Extensions
{ {
public static class SqlMapperExtensions public static partial class SqlMapperExtensions
{ {
public interface IProxy // ReSharper disable once MemberCanBePrivate.Global
public interface IProxy //must be kept public
{ {
bool IsDirty { get; set; } bool IsDirty { get; set; }
} }
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> KeyProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>(); private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> KeyProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> TypeProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>(); private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> TypeProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> ComputedProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>(); private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> ComputedProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> GetQueries = new ConcurrentDictionary<RuntimeTypeHandle, string>(); private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> GetQueries = new ConcurrentDictionary<RuntimeTypeHandle, string>();
private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> TypeTableName = new ConcurrentDictionary<RuntimeTypeHandle, string>(); private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> TypeTableName = new ConcurrentDictionary<RuntimeTypeHandle, string>();
private static readonly Dictionary<string, ISqlAdapter> AdapterDictionary = new Dictionary<string, ISqlAdapter>() { private static readonly Dictionary<string, ISqlAdapter> AdapterDictionary = new Dictionary<string, ISqlAdapter> {
{"sqlconnection", new SqlServerAdapter()}, {"sqlconnection", new SqlServerAdapter()},
{"npgsqlconnection", new PostgresAdapter()}, {"npgsqlconnection", new PostgresAdapter()},
{"sqliteconnection", new SQLiteAdapter()} {"sqliteconnection", new SQLiteAdapter()}
}; };
private static IEnumerable<PropertyInfo> ComputedPropertiesCache(Type type)
{ private static IEnumerable<PropertyInfo> ComputedPropertiesCache(Type type)
IEnumerable<PropertyInfo> pi; {
if (ComputedProperties.TryGetValue(type.TypeHandle, out pi)) IEnumerable<PropertyInfo> pi;
{ if (ComputedProperties.TryGetValue(type.TypeHandle, out pi))
return pi; {
} return pi.ToList();
}
var computedProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList();
var computedProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList();
ComputedProperties[type.TypeHandle] = computedProperties;
return computedProperties; ComputedProperties[type.TypeHandle] = computedProperties;
} return computedProperties;
private static IEnumerable<PropertyInfo> KeyPropertiesCache(Type type) }
private static List<PropertyInfo> KeyPropertiesCache(Type type)
{ {
IEnumerable<PropertyInfo> pi; IEnumerable<PropertyInfo> pi;
if (KeyProperties.TryGetValue(type.TypeHandle,out pi)) if (KeyProperties.TryGetValue(type.TypeHandle, out pi))
{ {
return pi; return pi.ToList();
} }
var allProperties = TypePropertiesCache(type); var allProperties = TypePropertiesCache(type);
...@@ -61,7 +63,7 @@ private static IEnumerable<PropertyInfo> KeyPropertiesCache(Type type) ...@@ -61,7 +63,7 @@ private static IEnumerable<PropertyInfo> KeyPropertiesCache(Type type)
if (keyProperties.Count == 0) if (keyProperties.Count == 0)
{ {
var idProp = allProperties.Where(p => p.Name.ToLower() == "id").FirstOrDefault(); var idProp = allProperties.FirstOrDefault(p => p.Name.ToLower() == "id");
if (idProp != null) if (idProp != null)
{ {
keyProperties.Add(idProp); keyProperties.Add(idProp);
...@@ -71,36 +73,36 @@ private static IEnumerable<PropertyInfo> KeyPropertiesCache(Type type) ...@@ -71,36 +73,36 @@ private static IEnumerable<PropertyInfo> KeyPropertiesCache(Type type)
KeyProperties[type.TypeHandle] = keyProperties; KeyProperties[type.TypeHandle] = keyProperties;
return keyProperties; return keyProperties;
} }
private static IEnumerable<PropertyInfo> TypePropertiesCache(Type type)
private static List<PropertyInfo> TypePropertiesCache(Type type)
{ {
IEnumerable<PropertyInfo> pis; IEnumerable<PropertyInfo> pis;
if (TypeProperties.TryGetValue(type.TypeHandle, out pis)) if (TypeProperties.TryGetValue(type.TypeHandle, out pis))
{ {
return pis; return pis.ToList();
} }
var properties = type.GetProperties().Where(IsWriteable).ToArray(); var properties = type.GetProperties().Where(IsWriteable).ToArray();
TypeProperties[type.TypeHandle] = properties; TypeProperties[type.TypeHandle] = properties;
return properties; return properties.ToList();
}
private static bool IsWriteable(PropertyInfo pi)
{
var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false);
if (attributes.Length != 1) return true;
var writeAttribute = (WriteAttribute)attributes[0];
return writeAttribute.Write;
} }
public static bool IsWriteable(PropertyInfo pi) /// <summary>
{ /// Returns a single entity by a single id from table "Ts".
object[] attributes = pi.GetCustomAttributes(typeof (WriteAttribute), false);
if (attributes.Length == 1)
{
WriteAttribute write = (WriteAttribute) attributes[0];
return write.Write;
}
return true;
}
/// <summary>
/// Returns a single entity by a single id from table "Ts". T must be of interface type.
/// Id must be marked with [Key] attribute. /// Id must be marked with [Key] attribute.
/// Created entity is tracked/intercepted for changes and used by the Update() extension. /// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension
/// for optimal performance.
/// </summary> /// </summary>
/// <typeparam name="T">Interface type to create and populate</typeparam> /// <typeparam name="T">Interface or type to create and populate</typeparam>
/// <param name="connection">Open SqlConnection</param> /// <param name="connection">Open SqlConnection</param>
/// <param name="id">Id of the entity to get, must be marked with [Key] attribute</param> /// <param name="id">Id of the entity to get, must be marked with [Key] attribute</param>
/// <returns>Entity of T</returns> /// <returns>Entity of T</returns>
...@@ -109,11 +111,11 @@ public static bool IsWriteable(PropertyInfo pi) ...@@ -109,11 +111,11 @@ public static bool IsWriteable(PropertyInfo pi)
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))
{ {
var keys = KeyPropertiesCache(type); var keys = KeyPropertiesCache(type);
if (keys.Count() > 1) if (keys.Count() > 1)
throw new DataException("Get<T> only supports an entity with a single [Key] property"); throw new DataException("Get<T> only supports an entity with a single [Key] property");
if (keys.Count() == 0) if (!keys.Any())
throw new DataException("Get<T> only supports en entity with a [Key] property"); throw new DataException("Get<T> only supports en entity with a [Key] property");
var onlyKey = keys.First(); var onlyKey = keys.First();
...@@ -125,18 +127,18 @@ public static bool IsWriteable(PropertyInfo pi) ...@@ -125,18 +127,18 @@ public static bool IsWriteable(PropertyInfo pi)
sql = "select * from " + name + " where " + onlyKey.Name + " = @id"; sql = "select * from " + name + " where " + onlyKey.Name + " = @id";
GetQueries[type.TypeHandle] = sql; GetQueries[type.TypeHandle] = sql;
} }
var dynParms = new DynamicParameters(); var dynParms = new DynamicParameters();
dynParms.Add("@id", id); dynParms.Add("@id", id);
T obj = null; T obj;
if (type.IsInterface) if (type.IsInterface)
{ {
var res = connection.Query(sql, dynParms).FirstOrDefault() as IDictionary<string, object>; var res = connection.Query(sql, dynParms).FirstOrDefault() as IDictionary<string, object>;
if (res == null) if (res == null)
return (T)((object)null); return null;
obj = ProxyGenerator.GetInterfaceProxy<T>(); obj = ProxyGenerator.GetInterfaceProxy<T>();
...@@ -150,11 +152,10 @@ public static bool IsWriteable(PropertyInfo pi) ...@@ -150,11 +152,10 @@ public static bool IsWriteable(PropertyInfo pi)
} }
else else
{ {
obj = connection.Query<T>(sql, dynParms, transaction: transaction, commandTimeout: commandTimeout).FirstOrDefault(); obj = connection.Query<T>(sql, dynParms, transaction, commandTimeout: commandTimeout).FirstOrDefault();
} }
return obj; return obj;
} }
private static string GetTableName(Type type) private static string GetTableName(Type type)
{ {
string name; string name;
...@@ -163,9 +164,9 @@ private static string GetTableName(Type type) ...@@ -163,9 +164,9 @@ private static string GetTableName(Type type)
name = type.Name + "s"; name = type.Name + "s";
if (type.IsInterface && name.StartsWith("I")) if (type.IsInterface && name.StartsWith("I"))
name = name.Substring(1); name = name.Substring(1);
//NOTE: This as dynamic trick should be able to handle both our own Table-attribute as well as the one in EntityFramework //NOTE: This as dynamic trick should be able to handle both our own Table-attribute as well as the one in EntityFramework
var tableattr = type.GetCustomAttributes(false).Where(attr => attr.GetType().Name == "TableAttribute").SingleOrDefault() as var tableattr = type.GetCustomAttributes(false).SingleOrDefault(attr => attr.GetType().Name == "TableAttribute") as
dynamic; dynamic;
if (tableattr != null) if (tableattr != null)
name = tableattr.Name; name = tableattr.Name;
...@@ -173,7 +174,7 @@ private static string GetTableName(Type type) ...@@ -173,7 +174,7 @@ private static string GetTableName(Type type)
} }
return name; return name;
} }
/// <summary> /// <summary>
/// Inserts an entity into table "Ts" and returns identity id. /// Inserts an entity into table "Ts" and returns identity id.
/// </summary> /// </summary>
...@@ -182,37 +183,33 @@ private static string GetTableName(Type type) ...@@ -182,37 +183,33 @@ private static string GetTableName(Type type)
/// <returns>Identity of inserted entity</returns> /// <returns>Identity of inserted entity</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 type = typeof(T); var type = typeof(T);
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);
var keyProperties = KeyPropertiesCache(type); var keyProperties = KeyPropertiesCache(type);
var computedProperties = ComputedPropertiesCache(type); var computedProperties = ComputedPropertiesCache(type);
var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)); var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count(); i++) for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count(); i++)
{ {
var property = allPropertiesExceptKeyAndComputed.ElementAt(i); var property = allPropertiesExceptKeyAndComputed.ElementAt(i);
sbColumnList.AppendFormat("[{0}]", property.Name); sbColumnList.AppendFormat("[{0}]", property.Name);
if (i < allPropertiesExceptKeyAndComputed.Count() - 1) if (i < allPropertiesExceptKeyAndComputed.Count() - 1)
sbColumnList.Append(", "); sbColumnList.Append(", ");
} }
var sbParameterList = new StringBuilder(null); var sbParameterList = new StringBuilder(null);
for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count(); i++) for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count(); i++)
{ {
var property = allPropertiesExceptKeyAndComputed.ElementAt(i); var property = allPropertiesExceptKeyAndComputed.ElementAt(i);
sbParameterList.AppendFormat("@{0}", property.Name); sbParameterList.AppendFormat("@{0}", property.Name);
if (i < allPropertiesExceptKeyAndComputed.Count() - 1) if (i < allPropertiesExceptKeyAndComputed.Count() - 1)
sbParameterList.Append(", "); sbParameterList.Append(", ");
} }
ISqlAdapter adapter = GetFormatter(connection); var adapter = GetFormatter(connection);
int id = adapter.Insert(connection, transaction, commandTimeout, name, sbColumnList.ToString(), sbParameterList.ToString(), keyProperties, entityToInsert); return adapter.Insert(connection, transaction, commandTimeout, name, sbColumnList.ToString(),
return id; sbParameterList.ToString(), keyProperties, entityToInsert);
} }
/// <summary> /// <summary>
...@@ -243,7 +240,7 @@ private static string GetTableName(Type type) ...@@ -243,7 +240,7 @@ private static string GetTableName(Type type)
var allProperties = TypePropertiesCache(type); var allProperties = TypePropertiesCache(type);
var computedProperties = ComputedPropertiesCache(type); var computedProperties = ComputedPropertiesCache(type);
var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties)); var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
for (var i = 0; i < nonIdProps.Count(); i++) for (var i = 0; i < nonIdProps.Count(); i++)
{ {
...@@ -273,13 +270,13 @@ private static string GetTableName(Type type) ...@@ -273,13 +270,13 @@ private static string GetTableName(Type type)
/// <returns>true if deleted, false if not found</returns> /// <returns>true if deleted, false if not found</returns>
public static bool Delete<T>(this IDbConnection connection, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null) where T : class public static bool Delete<T>(this IDbConnection connection, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{ {
if (entityToDelete == null) if (entityToDelete == null)
throw new ArgumentException("Cannot Delete null Object", "entityToDelete"); throw new ArgumentException("Cannot Delete null Object", "entityToDelete");
var type = typeof(T); var type = typeof(T);
var keyProperties = KeyPropertiesCache(type); var keyProperties = KeyPropertiesCache(type);
if (keyProperties.Count() == 0) 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");
var name = GetTableName(type); var name = GetTableName(type);
...@@ -294,11 +291,10 @@ private static string GetTableName(Type type) ...@@ -294,11 +291,10 @@ private static string GetTableName(Type type)
if (i < keyProperties.Count() - 1) if (i < keyProperties.Count() - 1)
sb.AppendFormat(" and "); sb.AppendFormat(" and ");
} }
var deleted = connection.Execute(sb.ToString(), entityToDelete, transaction: transaction, commandTimeout: commandTimeout); var deleted = connection.Execute(sb.ToString(), entityToDelete, transaction, commandTimeout);
return deleted > 0; return deleted > 0;
} }
/// <summary> /// <summary>
/// Delete all entities in the table related to the type T. /// Delete all entities in the table related to the type T.
/// </summary> /// </summary>
...@@ -310,20 +306,19 @@ private static string GetTableName(Type type) ...@@ -310,20 +306,19 @@ private static string GetTableName(Type type)
var type = typeof(T); var type = typeof(T);
var name = GetTableName(type); var name = GetTableName(type);
var statement = String.Format("delete from {0}", name); var statement = String.Format("delete from {0}", name);
var deleted = connection.Execute(statement, null, transaction: transaction, commandTimeout: commandTimeout); var deleted = connection.Execute(statement, null, transaction, commandTimeout);
return deleted > 0; return deleted > 0;
} }
private static ISqlAdapter GetFormatter(IDbConnection connection)
{
var name = connection.GetType().Name.ToLower();
return !AdapterDictionary.ContainsKey(name) ?
new SqlServerAdapter() :
AdapterDictionary[name];
}
public static ISqlAdapter GetFormatter(IDbConnection connection) static class ProxyGenerator
{
string name = connection.GetType().Name.ToLower();
if (!AdapterDictionary.ContainsKey(name))
return new SqlServerAdapter();
return AdapterDictionary[name];
}
class ProxyGenerator
{ {
private static readonly Dictionary<Type, object> TypeCache = new Dictionary<Type, object>(); private static readonly Dictionary<Type, object> TypeCache = new Dictionary<Type, object>();
...@@ -335,14 +330,6 @@ private static AssemblyBuilder GetAsmBuilder(string name) ...@@ -335,14 +330,6 @@ private static AssemblyBuilder GetAsmBuilder(string name)
return assemblyBuilder; return assemblyBuilder;
} }
public static T GetClassProxy<T>()
{
// A class proxy could be implemented if all properties are virtual
// otherwise there is a pretty dangerous case where internal actions will not update dirty tracking
throw new NotImplementedException();
}
public static T GetInterfaceProxy<T>() public static T GetInterfaceProxy<T>()
{ {
Type typeOfT = typeof(T); Type typeOfT = typeof(T);
...@@ -356,7 +343,7 @@ public static T GetInterfaceProxy<T>() ...@@ -356,7 +343,7 @@ public static T GetInterfaceProxy<T>()
var moduleBuilder = assemblyBuilder.DefineDynamicModule("SqlMapperExtensions." + typeOfT.Name); //NOTE: to save, add "asdasd.dll" parameter var moduleBuilder = assemblyBuilder.DefineDynamicModule("SqlMapperExtensions." + typeOfT.Name); //NOTE: to save, add "asdasd.dll" parameter
var interfaceType = typeof(Dapper.Contrib.Extensions.SqlMapperExtensions.IProxy); var interfaceType = typeof(IProxy);
var typeBuilder = moduleBuilder.DefineType(typeOfT.Name + "_" + Guid.NewGuid(), var typeBuilder = moduleBuilder.DefineType(typeOfT.Name + "_" + Guid.NewGuid(),
TypeAttributes.Public | TypeAttributes.Class); TypeAttributes.Public | TypeAttributes.Class);
typeBuilder.AddInterfaceImplementation(typeOfT); typeBuilder.AddInterfaceImplementation(typeOfT);
...@@ -390,7 +377,7 @@ private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder) ...@@ -390,7 +377,7 @@ private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder)
var property = typeBuilder.DefineProperty("IsDirty", var property = typeBuilder.DefineProperty("IsDirty",
System.Reflection.PropertyAttributes.None, System.Reflection.PropertyAttributes.None,
propType, propType,
new Type[] { propType }); new[] { propType });
const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.SpecialName | const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.SpecialName |
MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig; MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig;
...@@ -400,24 +387,24 @@ private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder) ...@@ -400,24 +387,24 @@ private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder)
getSetAttr, getSetAttr,
propType, propType,
Type.EmptyTypes); Type.EmptyTypes);
var currGetIL = currGetPropMthdBldr.GetILGenerator(); var currGetIl = currGetPropMthdBldr.GetILGenerator();
currGetIL.Emit(OpCodes.Ldarg_0); currGetIl.Emit(OpCodes.Ldarg_0);
currGetIL.Emit(OpCodes.Ldfld, field); currGetIl.Emit(OpCodes.Ldfld, field);
currGetIL.Emit(OpCodes.Ret); currGetIl.Emit(OpCodes.Ret);
var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + "IsDirty", var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + "IsDirty",
getSetAttr, getSetAttr,
null, null,
new Type[] { propType }); new[] { propType });
var currSetIL = currSetPropMthdBldr.GetILGenerator(); var currSetIl = currSetPropMthdBldr.GetILGenerator();
currSetIL.Emit(OpCodes.Ldarg_0); currSetIl.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1); currSetIl.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, field); currSetIl.Emit(OpCodes.Stfld, field);
currSetIL.Emit(OpCodes.Ret); currSetIl.Emit(OpCodes.Ret);
property.SetGetMethod(currGetPropMthdBldr); property.SetGetMethod(currGetPropMthdBldr);
property.SetSetMethod(currSetPropMthdBldr); property.SetSetMethod(currSetPropMthdBldr);
var getMethod = typeof(Dapper.Contrib.Extensions.SqlMapperExtensions.IProxy).GetMethod("get_" + "IsDirty"); var getMethod = typeof(IProxy).GetMethod("get_" + "IsDirty");
var setMethod = typeof(Dapper.Contrib.Extensions.SqlMapperExtensions.IProxy).GetMethod("set_" + "IsDirty"); var setMethod = typeof(IProxy).GetMethod("set_" + "IsDirty");
typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod); typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod);
typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod); typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod);
...@@ -431,7 +418,7 @@ private static void CreateProperty<T>(TypeBuilder typeBuilder, string propertyNa ...@@ -431,7 +418,7 @@ private static void CreateProperty<T>(TypeBuilder typeBuilder, string propertyNa
var property = typeBuilder.DefineProperty(propertyName, var property = typeBuilder.DefineProperty(propertyName,
System.Reflection.PropertyAttributes.None, System.Reflection.PropertyAttributes.None,
propType, propType,
new Type[] { propType }); new[] { propType });
const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.Virtual | const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.Virtual |
MethodAttributes.HideBySig; MethodAttributes.HideBySig;
...@@ -442,25 +429,25 @@ private static void CreateProperty<T>(TypeBuilder typeBuilder, string propertyNa ...@@ -442,25 +429,25 @@ private static void CreateProperty<T>(TypeBuilder typeBuilder, string propertyNa
propType, propType,
Type.EmptyTypes); Type.EmptyTypes);
var currGetIL = currGetPropMthdBldr.GetILGenerator(); var currGetIl = currGetPropMthdBldr.GetILGenerator();
currGetIL.Emit(OpCodes.Ldarg_0); currGetIl.Emit(OpCodes.Ldarg_0);
currGetIL.Emit(OpCodes.Ldfld, field); currGetIl.Emit(OpCodes.Ldfld, field);
currGetIL.Emit(OpCodes.Ret); currGetIl.Emit(OpCodes.Ret);
var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName, var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName,
getSetAttr, getSetAttr,
null, null,
new Type[] { propType }); new[] { propType });
//store value in private field and set the isdirty flag //store value in private field and set the isdirty flag
var currSetIL = currSetPropMthdBldr.GetILGenerator(); var currSetIl = currSetPropMthdBldr.GetILGenerator();
currSetIL.Emit(OpCodes.Ldarg_0); currSetIl.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1); currSetIl.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, field); currSetIl.Emit(OpCodes.Stfld, field);
currSetIL.Emit(OpCodes.Ldarg_0); currSetIl.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldc_I4_1); currSetIl.Emit(OpCodes.Ldc_I4_1);
currSetIL.Emit(OpCodes.Call, setIsDirtyMethod); currSetIl.Emit(OpCodes.Call, setIsDirtyMethod);
currSetIL.Emit(OpCodes.Ret); currSetIl.Emit(OpCodes.Ret);
//TODO: Should copy all attributes defined by the interface? //TODO: Should copy all attributes defined by the interface?
if (isIdentity) if (isIdentity)
...@@ -478,7 +465,6 @@ private static void CreateProperty<T>(TypeBuilder typeBuilder, string propertyNa ...@@ -478,7 +465,6 @@ private static void CreateProperty<T>(TypeBuilder typeBuilder, string propertyNa
typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod); typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod);
typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod); typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod);
} }
} }
} }
...@@ -489,7 +475,10 @@ public TableAttribute(string tableName) ...@@ -489,7 +475,10 @@ public TableAttribute(string tableName)
{ {
Name = tableName; Name = tableName;
} }
public string Name { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once UnusedAutoPropertyAccessor.Global
public string Name { get; set; }
} }
// do not want to depend on data annotations that is not in client profile // do not want to depend on data annotations that is not in client profile
...@@ -498,97 +487,98 @@ public class KeyAttribute : Attribute ...@@ -498,97 +487,98 @@ public class KeyAttribute : Attribute
{ {
} }
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]
public class WriteAttribute : Attribute public class WriteAttribute : Attribute
{ {
public WriteAttribute(bool write) public WriteAttribute(bool write)
{ {
Write = write; Write = write;
} }
public bool Write { get; private set; } public bool Write { get; private set; }
} }
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]
public class ComputedAttribute : Attribute public class ComputedAttribute : Attribute
{ {
} }
} }
public interface ISqlAdapter public partial interface ISqlAdapter
{ {
int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert); int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert);
} }
public class SqlServerAdapter : ISqlAdapter public partial class SqlServerAdapter : ISqlAdapter
{ {
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert) public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert)
{ {
string cmd = String.Format("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList); var cmd = String.Format("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList);
connection.Execute(cmd, entityToInsert, transaction: transaction, commandTimeout: commandTimeout);
//NOTE: would prefer to use SCOPE_IDENTITY but this are not available on SQLCE
var r = connection.Query("select @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout);
int id = (int)r.First().id;
if (keyProperties.Any())
keyProperties.First().SetValue(entityToInsert, id, null);
return id;
}
}
public class PostgresAdapter : ISqlAdapter connection.Execute(cmd, entityToInsert, transaction, commandTimeout);
{
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert) //NOTE: would prefer to use IDENT_CURRENT('tablename') or IDENT_SCOPE but these are not available on SQLCE
{ var r = connection.Query("select @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout);
StringBuilder sb = new StringBuilder(); var id = (int)r.First().id;
sb.AppendFormat("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList); var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (propertyInfos.Any())
// If no primary key then safe to assume a join table with not too much data to return propertyInfos.First().SetValue(entityToInsert, id, null);
if (!keyProperties.Any()) return id;
sb.Append(" RETURNING *"); }
else
{
sb.Append(" RETURNING ");
bool first = true;
foreach (var property in keyProperties)
{
if (!first)
sb.Append(", ");
first = false;
sb.Append(property.Name);
}
}
var results = connection.Query(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout);
// Return the key by assinging the corresponding property in the object - by product is that it supports compound primary keys
int id = 0;
foreach (var p in keyProperties)
{
var value = ((IDictionary<string, object>)results.First())[p.Name.ToLower()];
p.SetValue(entityToInsert, value, null);
if (id == 0)
id = Convert.ToInt32(value);
}
return id;
}
} }
public class SQLiteAdapter : ISqlAdapter public partial class PostgresAdapter : ISqlAdapter
{ {
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert) public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert)
{ {
string cmd = String.Format("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList); var sb = new StringBuilder();
sb.AppendFormat("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList);
// If no primary key then safe to assume a join table with not too much data to return
var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (!propertyInfos.Any())
sb.Append(" RETURNING *");
else
{
sb.Append(" RETURNING ");
var first = true;
foreach (var property in propertyInfos)
{
if (!first)
sb.Append(", ");
first = false;
sb.Append(property.Name);
}
}
connection.Execute(cmd, entityToInsert, transaction: transaction, commandTimeout: commandTimeout); var results = connection.Query(sb.ToString(), entityToInsert, transaction, commandTimeout: commandTimeout).ToList();
var r = connection.Query("select last_insert_rowid() id", transaction: transaction, commandTimeout: commandTimeout); // Return the key by assinging the corresponding property in the object - by product is that it supports compound primary keys
int id = (int)r.First().id; var id = 0;
if (keyProperties.Any()) foreach (var p in propertyInfos)
keyProperties.First().SetValue(entityToInsert, id, null); {
return id; var value = ((IDictionary<string, object>)results.First())[p.Name.ToLower()];
} p.SetValue(entityToInsert, value, null);
if (id == 0)
id = Convert.ToInt32(value);
}
return id;
}
}
public partial class SQLiteAdapter : ISqlAdapter
{
public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert)
{
var cmd = String.Format("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList);
connection.Execute(cmd, entityToInsert, transaction, commandTimeout);
var r = connection.Query("select last_insert_rowid() id", transaction: transaction, commandTimeout: commandTimeout);
var id = (int)r.First().id;
var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
if (propertyInfos.Any())
propertyInfos.First().SetValue(entityToInsert, id, null);
return id;
}
} }
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