24

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();  
}
John B
  • 20,062
  • 35
  • 120
  • 170
  • what does Line 1417 look like btw can you post your code example for others that maybe able to quickly help you..? – MethodMan Jan 24 '12 at 22:13
  • 1
    To be honest, I'd just use an intermediate type here - or the Non-generic Query API (which goes via "dynamic")... I have some ctor-code I could lift from protobuf-net, but I'm really not sure it would be beneficial... – Marc Gravell Jan 24 '12 at 22:15

3 Answers3

35

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.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Wow, I'm embarrassed that I totally missed that! – John B Jan 25 '12 at 13:33
  • 5
    This approach will force you to explicitly set all of the properties won't it? I can't, for instance, construct my type, then have Dapper run its course on the properties with the result set automatically? – crush Feb 12 '15 at 19:32
  • 5
    Not everyone works with Framework v4 yet, which is required for this solution that uses dynamic. It seems you can get away without dynamic if you use Dictionary indexing to get to your column values: thus where Marc has (int)row.Bar (row is dynamic in v4 of Dapper)), you can use (int)row["Bar"] (in Dapper 3.5 row is a Dictionary) and so on. It's not pleasant, but on some occasions perhaps necessary. – Nij Mar 12 '15 at 11:31
  • Unfortunately, it's still dynamic (slow) object.It could be good to have mapping like public AgragatedRootEntity(int id, string name, Options options, Dimensions dimensions ) without dynamic. – Artem A Oct 15 '20 at 17:59
  • @Artem you can cast to IDictionary if you don't like dynamic – Marc Gravell Oct 15 '20 at 19:34
  • strong type from dbtype -> dynamic-> strings? to cast and parse again? The correct implementation could be find the type of the constructor and its parameters(including classes) and create an instance but not via reflection or dynamic, but strongly typed – Artem A Oct 16 '20 at 10:51
4

I use this maybe it's help someone

YourList = connection.Query<YourQueryClass>(Query, arg)
              .Select(f => new ClassWithConstructor(f.foo,f.bar))
              .ToList();  
the_joric
  • 11,986
  • 6
  • 36
  • 57
hmfarimani
  • 531
  • 1
  • 8
  • 13
1

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.

Monso
  • 1,086
  • 8
  • 9