Reputation: 3123
I'm attempting to dynamically generate and execute the sql for Dapper with the aim to simply pass in a type and the sql is generated and executed dynamically.
Example classes:
public class User
{
[Key]
public int UserId { get; set; }
public Address Address { get; set; }
}
public class Address
{
[Key]
public int UserId { get; set; }
public string PostCode { get; set; }
}
Will effectively run the following:
// sql: "SELECT User.UserId, Address.UserId, Address.PostCode FROM User LEFT JOIN Address ON Address.User = User.UserId"... // auto generated from 'User' type including join to 'Address';
connection.Query<User, Address, User>(sql, /*** map argument needs to be dynamic Func<> ***/);
So given these types User
& Address
which are only known at runtime, how can I generate the appropriate delegate Func<User, Address, User>
to pass to the map
argument?
Func<User, Address, User> map = (u, a) => {
u.Address = a;
return u;
}
The examples I have seen for creating a Func<>
using reflection assume the types are known, in my case they aren't so the type arguments vary (Func<,> / Func<,,> / Func<,,,> etc).
Any help appreciated. I'll keep working through examples using expressions to see if anything sticks.
Upvotes: 2
Views: 904
Reputation: 381
This is as close as I've gotten. I tried to clone the Dapper repo to dig through how the Query() method works, but I'm using an older version of Visual Studio.
public static class DynamicFuncHelper
{
public static Delegate CreateFunc(Type type1, Type type2)
{
Type funcType = typeof(Func<,,>).MakeGenericType(type1, type2, type1);
MethodInfo method =
typeof(DynamicFuncHelper<,>)
.MakeGenericType(type1, type2)
.GetMethod("SetAddressProperty",
BindingFlags.Public | BindingFlags.Static
);
return Delegate.CreateDelegate(funcType, method);
}
}
public static class DynamicFuncHelper<T,U>
where T : class
where U : class
{
public static T SetAddressProperty(T obj1, U obj2)
{
obj1.GetType().InvokeMember("Address",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty,
Type.DefaultBinder, obj1, new[] { obj2 });
return obj1;
}
}
Passed this unit test
[TestClass]
public class DynamicFuncTest
{
[TestMethod]
public void TestDynamicMapper()
{
var actualUser = new User { UserId = 1 };
var actualAddress = new Address { PostCode = "12345", UserId = 1 };
var testSetAddress = DynamicFuncHelper.CreateFunc(typeof(User), typeof(Address));
var delegateResult = testSetAddress.DynamicInvoke(actualUser, actualAddress);
Assert.AreEqual(actualUser, delegateResult, "Delegate result was not actualUser");
Assert.AreEqual(actualAddress, actualUser.Address, "User address was not expected address");
}
}
Upvotes: 1