Dapper和匿名类型

39

是否可以在Dapper中使用匿名类型?

我知道你可以使用动态类型,例如:

connection.Query<dynamic>(blah, blah, blah) 

那么,是否有可能进行以下操作呢?
.Select(p=> new { A, B ,C }) 

或者之后的某些变化?

编辑

我想向您展示我目前如何使用Dapper。我倾向于缓存(使用InMemoryCache)数据,因此我只在开始时执行一个大查询(使用Dapper非常快),然后我使用Linq在我的Repository中对其进行排序。

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Common;
using System.Linq;
using Dapper;

namespace SomeNamespace.Data
{
public class DapperDataContext : IDisposable
{
    private readonly string _connectionString;
    private readonly DbProviderFactory _provider;
    private readonly string _providerName;

    public DapperDataContext()
    {
        const string connectionStringName = " DataContextConnectionString";
        _connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
        _providerName = ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName;
        _provider = DbProviderFactories.GetFactory(_providerName);
    }

    public IEnumerable<MyDataView> MyData1 { get; private set; }
    public IEnumerable<MyDataView> MyData2 { get; private set; }

    protected string SqlSelectMyTable1Query
    {
        get
        {
            return @"SELECT Id, A, B, C from table1Name";
        }
    }   


protected string SqlSelectMyTable2Query
{
    get
    {
    return @"SELECT Id, A, B, C from table2Name";
    }
    }

    public void Dispose()
    {
    }

    public void Refresh()
    {
        using (var connection = _provider.CreateConnection())
        {
            // blow up if null
            connection.ConnectionString = _connectionString;
            connection.Open();

            var sql = String.Join(" ",
                            new[]
                                {
                                    SqlSelectMyTable1Query,
                                    SqlSelectMyTable2Query
                                });

            using (var multi = connection.QueryMultiple(sql))
            {
                MyData1 = multi.Read<MyDataView>().ToList();
                MyData2 = multi.Read<MyDataView>().ToList();
            }
        }
    }

    public class MyDataView
    {
        public long Id { get; set; }
        public string A { get; set; }
        public string B { get; set; }
        public string C { get; set; }
    }      
}
}

内存缓存看起来像这样

namespace Libs.Web
{
public class InMemoryCache : ICacheService
{
    #region ICacheService Members

    public T Get<T>(string cacheId, Func<T> getItemCallback) where T : class
    {
        var item = HttpRuntime.Cache.Get(cacheId) as T;
        if (item == null)
        {
            item = getItemCallback();
            HttpContext.Current.Cache.Insert(cacheId, item);
        }
        return item;
    }

    public void Clear(string cacheId)
    {
        HttpContext.Current.Cache.Remove(cacheId);
    }

    #endregion
}

public interface ICacheService
{
    T Get<T>(string cacheId, Func<T> getItemCallback) where T : class;
    void Clear(string cacheId);
}
}

是的,我有点预料到会出现这种情况,但目前还没有任何进展。var result = multi.Read().Select((p)=> new {Id = p["Id"]}).ToList(); - Peter
常量字符串testsql = @"SELECT Id FROM table ;"; var result = connection.Query(testsql).Select((p) => new { Id = p.Id }); - Peter
如果您确实是通过Select方法来查询,那么它应该能够按照编写的方式工作,尤其是如果您将属性强制转换为相应类型。我会在有时间时进行测试。 - Marc Gravell
如果答案与我相关,我会点赞这个问题,但在我看来它并不相关。我想要没有动态中间件的匿名类型。在我制作的一个小映射器上,我使用connection.Query("SELECT * FROM Person").MapTo(() => new { Id = default(int), Name = default(string), Age = default(int?)})来声明匿名类型,我希望Dapper内部也有类似的东西 :) - Guillaume86
答案给出是正确的,因此我不明白您为什么不愿意点赞。关于您提到的其他问题,我完全同意。 - Peter
5个回答

47

是否可以在Dapper中使用匿名类型?

可以,可以使用非泛型的Query重载方法,它返回一个dynamic类型的IDictionary<string, object>对象。这个对象是动态扩展类型,可以进行强制转换或用点表示法访问。

例如:

var v = connection.Query("select 1 as a, 2 as b").First(); 
Console.Write("{0} {1}",v.a, v.b) // prints: 1 2

那么,是否可以进行.Select操作呢?

当然可以,您会得到一个IEnumerable<dynamic>类型的对象,您可以对其运行任何您想要的操作。


所以你的意思是这样的:const string testsql = @"SELECT Id FROM table ;"; var result = connection.Query(testsql).ToList().Select((p) => new { Id = p.Id });可以工作吗? - Peter
1
@Peter 是的,虽然你可以更简洁一些 :) connection.Query(testsql).Select((p) => new { (int)p.Id }); 如果你只选择 Id,你也可以使用 connection.Query<int>(testsql).Select((Id) => new { Id }); - Sam Saffron
当我执行以下代码时 const string testsql = @"SELECT form_id as Id FROM form ;"; connection.Query(testsql).Select((p) => new { (int)p.Id });,我会得到编译时错误“无效的匿名类型成员声明器。匿名类型成员必须使用成员赋值、简单名称或成员访问来声明。”所以我尝试了这个 var x = connection.Query<dynamic>(testsql).Select((p) => new { Id = (int)p.Id });,但我得到了运行时错误“System.Dynamic.DynamicObject”不包含“Id”的定义。 - Peter
@Peter 你正在选择 form_id,所以你需要将其更改为:new { Id = (int)p.form_id } - Sam Saffron

41

以下是使用Dapper库与匿名类型的另一种解决方案:

public static class DapperExtensions
{
    public static IEnumerable<T> Query<T>(this IDbConnection connection, Func<T> typeBuilder, string sql)
    {
        return connection.Query<T>(sql);
    }
}

并像这样使用:

var data = connection.Query(() => new 
{
    ContactId = default(int),
    Name = default(string),
}, "SELECT ContactId, Name FROM Contact");

1
那么,如果扩展方法中甚至没有使用它,typeBuilder 的目的是什么? - Deilan
它是通过<T>来使用的。 - Guillaume86
应该是 ", T typebuilder, " 吧? - Bruno Santos
1
这非常优雅。我不确定它是否比当前接受的答案更好,还是只是个人口味的问题,但我会使用这个答案。 - Marc L.
@BrunoSantos,不,使用Func<T>时,T是从返回类型推断出来的。对于T,您需要使用具体类型,即非匿名类型,因此T在这里无法工作(这就是将其替换的内容)。 - Ofiris
如果你遇到了像 A parameterless default constructor or one matching signature 这样的异常,请尝试切换到基于 ValueTuple 的方法:https://dev59.com/PW025IYBdhLWcg3wNTEV#71400985/ - n0099

8

对Guillaume86的优秀解决方案进行了小幅改进:

public static IEnumerable<T> Query<T>(this IDbConnection connection, Func<T> typeBuilder,
    string sql, dynamic param = null, IDbTransaction transaction = null,
    bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
{
    return SqlMapper.Query<T>(connection, sql, param, transaction, buffered,
        commandTimeout, commandType);
}

它会被包含在Dapper中吗? - Serg
任何人都可以在项目网站 https://github.com/StackExchange/Dapper/issues 上为其创建一个问题。 - pistipanko
什么是SqlMapper? - Terry
https://github.com/StackExchange/Dapper/blob/main/Dapper/SqlMapper.cs - pistipanko

6

另一种方法是使用C# 7.0的新特性之一:元组类型

var users = dbConnection
    .Query<(Guid UserId, string Username)>("SELECT UserId, Username From MyUsers")
    .AsList();

这是一个基于结构的 ValueTuple,因此应该与 https://dev59.com/T4Xca4cB1Zd3GeqPECQd 中使用的原始 Tuple 类区分开来。 - n0099

1

尝试了Guillaume86的方法,但是我得到了“对象必须实现IConvertible”的错误。

所以我采用了另一种方法:

public static class DapperExtensions
{
    public static IEnumerable<T> QueryTyped<T>(
        this IDbConnection connection,
        string sql,
        Func<dynamic, T> typeBuilder)
    {
        var items = connection.Query<dynamic>(sql);
        var result = items.Select(typeBuilder);
        return result;
    }
}

使用方法:

var rows = connection.QueryTyped(sql,
    typeBuilder: (d) => new
    {
        Code = d.Code,
        Name = d.Name,
        FullName = d.Name + " " + d.LastName,
    });

这允许您映射甚至是 C# 函数(如果需要的话)。


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