'Call custom constructor with Dapper?
I'm trying to use Dapper to interface with the ASP.NET SQL Membership Provider tables. I wrapped the SqlMembershipProvider class and added an additional method to get me the MembershipUsers given a certain criteria relating to some custom tables I have.
When querying the data with Dapper, it appears that Dapper first instantiates the class with a parameter-less constructor, and then "maps" the returned columns into the properties on the object.
However, the UserName property on the MembershipUser class has a no setter. Judging from around line 1417 in the Dapper SqlMapper.cs, the method GetSettableProps()
only gets settable properties.
I tried to do a MultiMap query to invoke the constructor, but the problem with that is the objects passed into the query are already missing the UserName.
I'm guessing I could modify the GetSettableProps()
method, but I'm not sure if that will work, or if it will affect my existing code.
Is there anyway for me to invoke the custom constructor that the MembershipUser class has?
Or is there a reasonable change that I could make to Dapper to support my situation?
** UPDATE **
Marc's answer to use the non-generic/dynamic Query() method was correct, but for posterity, this is the method I was referring to inside Dapper:
static List<PropInfo> GetSettableProps(Type t)
{
return t
.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(p => new PropInfo
{
Name = p.Name,
Setter = p.DeclaringType == t ? p.GetSetMethod(true) : p.DeclaringType.GetProperty(p.Name).GetSetMethod(true),
Type = p.PropertyType
})
.Where(info => info.Setter != null)
.ToList();
}
Solution 1:[1]
I would use the non-generic Query API here...
return connection.Query(sql, args).Select(row =>
new AnyType((string)row.Foo, (int)row.Bar) {
Other = (float)row.Other }).ToList();
Using this you can use both non-default constructors and property assignments, without any changes, by using "dynamic" as an intermediate step.
Solution 2:[2]
I use this maybe it's help someone
YourList = connection.Query<YourQueryClass>(Query, arg)
.Select(f => new ClassWithConstructor(f.foo,f.bar))
.ToList();
Solution 3:[3]
I came across this question and it inspired me to think a bit differently than the other answers.
My approach:
internal class SqlMyObject: MyObject {
public SqlMyObject(string foo, int bar, float other): base(foo, bar, other) { }
}
...
return connection.Query<SqlMyObject>(sql, args);
Using a "private" derived class (I used internal
to expose it to Unit Tests, personal preference) I was able to specify one constructor that the derived class has, this can also do some mapping or business logic to get to the desired base class/params as necessary; this is useful if the only time that object should be initiated in that way is when it's pulling from the DB.
Also, can use Linq's .Cast<MyObject>()
on the collection to eliminate any type check issues down the line if need be without having to check for derived/base types.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Marc Gravell |
Solution 2 | the_joric |
Solution 3 | Monso |