使用Dapper调用自定义构造函数?

24
我正在尝试使用Dapper与ASP.NET SQL Membership Provider表进行接口交互。我包装了SqlMembershipProvider类,并添加了一个附加方法,以便在涉及到我的某些自定义表的特定条件下获取MembershipUsers。
在使用Dapper查询数据时,似乎Dapper首先使用无参数构造函数实例化类,然后将返回的列“映射”到对象上的属性。
但是,MembershipUser类上的UserName属性没有setter。从Dapper SqlMapper.cs中的第1417行左右判断,GetSettableProps()方法仅获取可设置的属性。
我尝试进行MultiMap查询以调用构造函数,但问题在于传递到查询中的对象已经缺少UserName。
我猜我可以修改GetSettableProps()方法,但我不确定它是否有效,或者它是否会影响我的现有代码。
有没有办法调用MembershipUser类的自定义构造函数?
还是有合理的更改可以使Dapper支持我的情况?
**更新**
Marc建议使用非泛型/动态Query()方法是正确的,但为了记录,这是我在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();  
}

1417行是什么样子?顺便能否发布你的代码示例,以便其他人可以快速帮助你吗? - MethodMan
1
说实话,在这里我会使用一个中间类型,或者使用非泛型查询 API(通过 "dynamic" 进行)...... 我有一些构造函数代码可以从 protobuf-net 中提取,但我真的不确定它是否有益... - Marc Gravell
3个回答

35

我会在这里使用非泛型的 Query API...

 return connection.Query(sql, args).Select(row =>
     new AnyType((string)row.Foo, (int)row.Bar) {
         Other = (float)row.Other }).ToList();

使用“dynamic”作为中间步骤,您可以同时使用非默认构造函数和属性赋值,而无需进行任何更改。


哇,我感到很尴尬,我完全忽略了那个! - John B
5
这种方法会强制你显式地设置所有属性,是吗?例如,我不能构造我的类型,然后让Dapper自动处理结果集中的属性,对吧? - crush
5
并非所有人都使用v4框架,而此解决方案需要它来使用dynamic。如果您使用词典索引来获取列值,则似乎可以避免使用dynamic:因此,在Dapper的v4版本中,Marc使用(int)row.Bar(row是动态的)),您可以使用(int)row["Bar"](在Dapper 3.5中,row是一个字典)等等。这并不令人愉快,但在某些情况下可能是必要的。 - Nij
不幸的是,它仍然是动态(缓慢)对象。如果没有动态的话,像public AgragatedRootEntity(int id, string name, Options options, Dimensions dimensions)这样的映射可能会很好。 - Artem A
@Artem 如果你不喜欢 dynamic,你可以转换为 IDictionary<string, object>。 - Marc Gravell
将从dbtype获得的强类型转换为动态字符串?需要再次进行强制转换和解析吗?正确的实现方式是找到构造函数及其参数(包括类)的类型,并创建一个实例,而不是通过反射或动态方式,而是强类型方式。 - Artem A

4
我使用这个,也许可以帮助其他人。
YourList = connection.Query<YourQueryClass>(Query, arg)
              .Select(f => new ClassWithConstructor(f.foo,f.bar))
              .ToList();  

1
我发现了这个问题,它启发了我与其他答案有所不同的思考方式。
我的方法是:
internal class SqlMyObject: MyObject {
    public SqlMyObject(string foo, int bar, float other): base(foo, bar, other) { }
}

...

return connection.Query<SqlMyObject>(sql, args);

使用“私有”派生类(我使用了internal将其暴露给单元测试,个人偏好),我能够指定派生类具有的一个构造函数,这也可以执行一些映射或业务逻辑以获得所需的基类/参数;如果该对象只有在从数据库中提取时才应以那种方式初始化,那么这是有用的。
此外,可以在集合上使用Linq的.Cast<MyObject>()来消除后续需要检查派生/基本类型的任何类型检查问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接