Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TypeMapperBuilder to use Dapper without setting Attributes on Model classes #463

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.vs/
bin/
obj/
artifacts/
/*.user
_Resharper*
.hgtags
Expand Down
31 changes: 31 additions & 0 deletions Dapper.Contrib/ReflectionHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Linq.Expressions;
using System.Reflection;

namespace Dapper.Contrib
{
public static class ReflectionHelper
{
public static MemberInfo GetProperty(LambdaExpression lambda)
{
Expression expr = lambda;
for (;;)
{
switch (expr.NodeType)
{
case ExpressionType.Lambda:
expr = ((LambdaExpression)expr).Body;
break;
case ExpressionType.Convert:
expr = ((UnaryExpression)expr).Operand;
break;
case ExpressionType.MemberAccess:
MemberExpression memberExpression = (MemberExpression)expr;
MemberInfo mi = memberExpression.Member;
return mi;
default:
return null;
}
}
}
}
}
121 changes: 121 additions & 0 deletions Dapper.Contrib/SqlMapperBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Linq.Expressions;
using Dapper.Contrib.Extensions;

namespace Dapper.Contrib
{
public static class TypeMappers
{
public static void Register<T>(TypeMapper<T> mapper) where T : class
{
}

public static ITypeMapperBuilder<T> MapType<T>() where T : class
{
return new CommonTypeMapper<T>();
}

public static void ResetTypeMapping<T>()
{
var typeHandle = typeof(T).TypeHandle;
IEnumerable<PropertyInfo> abc;
string tableName;
SqlMapperExtensions.InternalComputedProperties.TryRemove(typeHandle, out abc);
SqlMapperExtensions.InternalExplicitKeyProperties.TryRemove(typeHandle, out abc);
SqlMapperExtensions.InternalKeyProperties.TryRemove(typeHandle, out abc);
SqlMapperExtensions.InternalTypeProperties.TryRemove(typeHandle, out abc);
SqlMapperExtensions.InternalTypeTableName.TryRemove(typeHandle, out tableName);
}
}

public partial interface ITypeMapperBuilder<T> where T : class
{
ITypeMapperBuilder<T> TableName(string name);
IPropertyMapperBuilder<T> For(Expression<Func<T, object>> expression);
ITypeMapperBuilder<T> Key(Expression<Func<T, object>> expression);
}

public partial interface IPropertyMapperBuilder<T> where T : class
{
IPropertyMapperBuilder<T> For(Expression<Func<T, object>> expression);
IPropertyMapperBuilder<T> ExplicitKey();
IPropertyMapperBuilder<T> NotWritable();
IPropertyMapperBuilder<T> Computed();
}

internal class CommonTypeMapper<T> : TypeMapper<T> where T : class
{
}

public abstract partial class TypeMapper<T> : ITypeMapperBuilder<T>, IPropertyMapperBuilder<T> where T : class
{
private static readonly RuntimeTypeHandle TypeHandle = typeof(T).TypeHandle;
private PropertyInfo _currentProperty;

private static void TryAdd(IDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> dic, PropertyInfo property)
{
if (!dic.ContainsKey(TypeHandle))
{
dic[TypeHandle] = new List<PropertyInfo>
{
property
};
}
else
{
var list = new List<PropertyInfo>(dic[TypeHandle]) {property};
dic[TypeHandle] = list;
}
}

private static PropertyInfo GetProperty(Expression<Func<T, object>> expression)
{
return ReflectionHelper.GetProperty(expression) as PropertyInfo;
}

public ITypeMapperBuilder<T> TableName(string name)
{
SqlMapperExtensions.InternalTypeTableName[typeof(T).TypeHandle] = name;
return this;
}

public IPropertyMapperBuilder<T> For(Expression<Func<T, object>> expression)
{
_currentProperty = GetProperty(expression);
return this;
}

public ITypeMapperBuilder<T> Key(Expression<Func<T, object>> expression)
{
TryAdd(SqlMapperExtensions.InternalKeyProperties, GetProperty(expression));
return this;
}


public IPropertyMapperBuilder<T> ExplicitKey()
{
TryAdd(SqlMapperExtensions.InternalExplicitKeyProperties, _currentProperty);
return this;
}

public IPropertyMapperBuilder<T> NotWritable()
{
var properties = SqlMapperExtensions.InternalTypePropertiesCache(typeof (T));
var matchProperty = properties.FirstOrDefault(p => p.Equals(_currentProperty));
if (matchProperty != null && properties.Remove(matchProperty))
{
SqlMapperExtensions.InternalTypeProperties[TypeHandle] = properties;
}
return this;
}

public IPropertyMapperBuilder<T> Computed()
{
TryAdd(SqlMapperExtensions.InternalComputedProperties, _currentProperty);
return this;
}
}
}
14 changes: 14 additions & 0 deletions Dapper.Contrib/SqlMapperExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,20 @@ public WriteAttribute(bool write)
public class ComputedAttribute : Attribute
{
}

public static partial class SqlMapperExtensions
{
internal static ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> InternalKeyProperties { get { return KeyProperties; } }
internal static ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> InternalExplicitKeyProperties { get { return ExplicitKeyProperties; } }
internal static ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> InternalComputedProperties { get { return ComputedProperties; } }
internal static ConcurrentDictionary<RuntimeTypeHandle, string> InternalTypeTableName { get { return TypeTableName; } }
internal static ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> InternalTypeProperties { get { return TypeProperties; } }

internal static List<PropertyInfo> InternalTypePropertiesCache(Type type)
{
return TypePropertiesCache(type);
}
}
}

public partial interface ISqlAdapter
Expand Down
88 changes: 85 additions & 3 deletions Dapper.Tests.Contrib/TestSuite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;

using Dapper.Contrib;
using Dapper.Contrib.Extensions;

#if COREFX
using System.Reflection;
using IDbConnection = System.Data.Common.DbConnection;
Expand Down Expand Up @@ -91,10 +90,26 @@ public class Result
public int Order { get; set; }
}

public class Vehicle
{
public int Id { get; set; }
public string Name { get; set; }
public string Computed { get; set; }
}

public class ObjectXWithoutAttribute
{
public string ObjectXId { get; set; }
public string Name { get; set; }
public string NotWritable { get; set; }
}

public abstract partial class TestSuite
{
protected static readonly bool IsAppVeyor = Environment.GetEnvironmentVariable("Appveyor")?.ToUpperInvariant() == "TRUE";

protected abstract Type SqlClientExceptionType { get; }

public abstract IDbConnection GetConnection();

private IDbConnection GetOpenConnection()
Expand Down Expand Up @@ -174,7 +189,36 @@ public void InsertGetUpdateDeleteWithExplicitKey()
o2.IsNull();
}
}


[Fact]
public void InsertGetUpdateDeleteWithExplicitKey_UsingBuilder()
{
TypeMappers
.MapType<ObjectXWithoutAttribute>()
.TableName("ObjectX")
.For(x => x.ObjectXId).ExplicitKey()
.For(x => x.NotWritable).NotWritable();

using (var connection = GetOpenConnection())
{
var guid = Guid.NewGuid().ToString();
var o1 = new ObjectXWithoutAttribute { ObjectXId = guid, Name = "Foo" };
var originalxCount = connection.Query<int>("Select Count(*) From ObjectX").First();
connection.Insert(o1);
var list1 = connection.Query<ObjectXWithoutAttribute>("select * from ObjectX").ToList();
list1.Count.IsEqualTo(originalxCount + 1);
o1 = connection.Get<ObjectXWithoutAttribute>(guid);
o1.ObjectXId.IsEqualTo(guid);
o1.Name = "Bar";
connection.Update(o1);
o1 = connection.Get<ObjectXWithoutAttribute>(guid);
o1.Name.IsEqualTo("Bar");
connection.Delete(o1);
o1 = connection.Get<ObjectXWithoutAttribute>(guid);
o1.IsNull();
}
}

[Fact]
public void GetAllWithExplicitKey()
{
Expand Down Expand Up @@ -258,6 +302,44 @@ public void TableName()
}
}


public class VehicleMapper : TypeMapper<Vehicle>
{
public VehicleMapper()
{
this
.TableName("Automobiles")
.For(x => x.Computed).Computed();
}
}

[Fact]
public void TableName_UsingBuilder()
{
TypeMappers.Register(new VehicleMapper());
using (var connection = GetOpenConnection())
{
// tests against "Automobiles" table (Table attribute)
var id = connection.Insert(new Vehicle { Name = "Prado" });
var car = connection.Get<Vehicle>(id);
car.IsNotNull();
car.Name.IsEqualTo("Prado");
connection.Get<Vehicle>(id).Name.IsEqualTo("Prado");
connection.Update(new Vehicle { Id = (int)id, Name = "Prado Landcruiser" }).IsEqualTo(true);
connection.Get<Vehicle>(id).Name.IsEqualTo("Prado Landcruiser");
connection.Delete(new Vehicle { Id = (int)id }).IsEqualTo(true);
connection.Get<Vehicle>(id).IsNull();
}
TypeMappers.ResetTypeMapping<Vehicle>();

using (var connection = GetOpenConnection())
{
var ex = Xunit.Assert.Throws(SqlClientExceptionType, () => { connection.Insert(new Vehicle { Name = "Prado" }); });
Xunit.Assert.Contains("vehicles", ex.Message.ToLower());
}
}


[Fact]
public void TestSimpleGet()
{
Expand Down
9 changes: 5 additions & 4 deletions Dapper.Tests.Contrib/TestSuites.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class SqlServerTestSuite : TestSuite
? @"Server=(local)\SQL2014;Database=tempdb;User ID=sa;Password=Password12!"
: $"Data Source=.;Initial Catalog={DbName};Integrated Security=True";
public override IDbConnection GetConnection() => new SqlConnection(ConnectionString);

protected override Type SqlClientExceptionType { get { return typeof(System.Data.SqlClient.SqlException); } }
static SqlServerTestSuite()
{
using (var connection = new SqlConnection(ConnectionString))
Expand Down Expand Up @@ -76,7 +76,7 @@ public override IDbConnection GetConnection()
if (_skip) throw new SkipTestException("Skipping MySQL Tests - no server.");
return new MySqlConnection(ConnectionString);
}

protected override Type SqlClientExceptionType { get { return typeof(MySql.Data.MySqlClient.MySqlException); } }
private static readonly bool _skip;

static MySqlServerTestSuite()
Expand Down Expand Up @@ -127,7 +127,7 @@ public class SQLiteTestSuite : TestSuite
const string FileName = "Test.DB.sqlite";
public static string ConnectionString => $"Filename={FileName};";
public override IDbConnection GetConnection() => new SqliteConnection(ConnectionString);

protected override Type SqlClientExceptionType { get { return typeof(System.Data.SQLite.SQLiteException); } }
static SQLiteTestSuite()
{
if (File.Exists(FileName))
Expand Down Expand Up @@ -157,7 +157,8 @@ public class SqlCETestSuite : TestSuite
const string FileName = "Test.DB.sdf";
public static string ConnectionString => $"Data Source={FileName};";
public override IDbConnection GetConnection() => new SqlCeConnection(ConnectionString);

protected override Type SqlClientExceptionType { get { return typeof (System.Data.SqlServerCe.SqlCeException); } }

static SqlCETestSuite()
{
if (File.Exists(FileName))
Expand Down
3 changes: 2 additions & 1 deletion Dapper.Tests.Contrib/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@
"System.Data.Linq": "4.0.0.0",
"System.Runtime": "4.0.0.0",
"System.Transactions": "4.0.0.0",
"System.Xml": "4.0.0.0"
"System.Xml": "4.0.0.0",
"System.Threading.Tasks": "4.0.0.0"
},
"dependencies": {
"Microsoft.SqlServer.Compact": "4.0.8876.1",
Expand Down