如何使用Dapper和Linq?

20

我希望通过将Entity Framework转换为Dapper来提高数据访问性能。

我使用的查询是以谓词的形式,例如Expression<Func<TModel, bool>>

举个例子:

我需要将以下代码转换为使用Dapper:

目前我的做法是:

public async Task<List<TModel>> Get(Expression<Func<TModel, bool>> query)
{
    // this.Context is of type DbContext
    return await this.Context.Set<TModel>().Where(query).ToListAsync();
}

我想要做的事情:

public async Task<List<TModel>> Get(Expression<Func<TModel, bool>> query)
{
    using (IDbConnection cn = this.GetConnection)
    {
        return await cn.QueryAsync<TModel>(query);
    }
}

我的谷歌能力有限,可以有人帮忙吗。

编辑:

请注意,我找到了:https://github.com/ryanwatson/Dapper.Extensions.Linq

但是我似乎无法弄清如何使用它。


1
LINQ to EF 从实体模型和 LINQ 谓词构建 SQL 语句。Dapper 执行实际的 SQL 语句并"简单地"映射结果。你需要编写完整的参数化 SQL 语句。 - Panagiotis Kanavos
1
换句话说,你确定你没有解决“错误的问题”吗?你可以简单地向任何查询中添加另一个Where子句,例如var query=context.SomeEntity.Where()....; query=query.Where(...); query=query.Select(...);。如果你只想向现有查询添加过滤器,那么你不需要使用表达式。转移到微型ORM并不能使运行时构建查询更容易。 - Panagiotis Kanavos
对于Dapper,cn.QueryAsync<TModel>(query)中的query应该是SQL字符串,而不是Expression<Func<TModel,bool>>。在从EF转换到Dapper时,需要进行重大更改。 - M.Hassan
2个回答

26

首先,Dapper的其中一位作者表示,在有人问到

是否有计划使Dapper.net与IQueryable接口兼容?

时,回答说

没有这方面的计划。这远远超出了 Dapper 尝试做的事情。我甚至会说这是相对立的。 Dapper 的核心是要成为 SQL 爱好者的朋友。

(参见https://dev59.com/XF4c5IYBdhLWcg3wv8ll#27588877)。

某种程度上,这确实表明,各种NuGet扩展包可能会像你所推荐的那样有所帮助。

我曾尝试使用DapperExtensions,它使编写程序化查询过滤器变得更加容易 - 例如:

using System.Data.SqlClient;
using DapperExtensions;

namespace StackOverflowAnswer
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var cn = new SqlConnection("Server=.;Database=NORTHWND;Trusted_Connection=True;"))
            {
                var list = cn.GetList<Products>(
                    Predicates.Field<Products>(f => f.Discontinued, Operator.Eq, false)
                );
            }
        }

        class Products
        {
            public int ProductId { get; set; }
            public string ProductName { get; set; }
            public bool Discontinued { get; set; }
        }
    }
}

我也尝试了你建议的Dapper.Extensions.Linq包,它承诺通过Linq查询提供高级数据库访问。其流畅的配置使设置简单而快速。

不幸的是,我在使用它时也没有走得太远。文档很少,并且测试似乎没有覆盖QueryBuilder,这似乎是将Linq表达式转换为Dapper扩展谓词所使用的类(正如问题Parsing boolean expressions with the QueryBuilder所建议的)。我尝试了下面的方法,需要将IEntity接口添加到我的DTO中-

using System;
using System.Data.SqlClient;
using System.Linq.Expressions;
using Dapper.Extensions.Linq.Builder;
using Dapper.Extensions.Linq.Core;
using DapperExtensions;

namespace StackOverflowAnswer
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var cn = new SqlConnection("Server=.;Database=NORTHWND;Trusted_Connection=True;"))
            {
                Expression<Func<Products, bool>> filter = p => !p.Discontinued;
                var queryFilter = QueryBuilder<Products>.FromExpression(filter);

                var list = cn.GetList<Products>(
                    queryFilter
                );
            }
        }

        class Products : IEntity
        {
            public int ProductId { get; set; }
            public string ProductName { get; set; }
            public bool Discontinued { get; set; }
        }
    }
}

..但在运行时出现错误

未找到StackOverflowAnswer.Program+Products的运算符

我不确定为什么手动生成谓词(第一个示例)有效,但QueryBuilder却无效。

我想说的是,越来越像是你问题上的评论是正确的,你需要重新设计代码以远离使用Entity Framework的表达式。由于很难找到有关这个 QueryBuilder 类的任何信息,因此我担心即使你让它工作了,你遇到的任何问题都将难以获得帮助(而且可能会出现错误无法修复的情况)。


2
感谢您抽出时间查看我的问题。我很高兴(也很难过)有人得出了与我相同的结论。非常感谢。 - Rian Mostert
4
补充一下,尽管Dapper的作者可能认为将LINQ扩展到Dapper中与其预期目的“背道而驰”,但我相信这样做有很好的理由和好处。首先,这样的库提供了内置的防止SQL注入攻击的保护。其次,您可以获得智能提示的好处。您可以在不引入Entity Framework的漏洞和开销堆栈的情况下,获得所有这些优势以及Dapper的稳定性和可靠性。手写纯SQL并不总是选择Dapper作为ORM的决定因素。 - CShark

18

我写了一个工具,使用属性来让EF与Dapper一起工作。我解析谓词并将其转换为SQL。

"用户" POCO:

[Table("Users")]
public class User
{
    [Key]
    [Identity]
    public int Id { get; set; }

    public string Login { get; set;}

    [Column("FName")]
    public string FirstName { get; set; }

    [Column("LName")]
    public string LastName { get; set; }

    public string Email { get; set; }

    [NotMapped]
    public string FullName
    {
        get
        {
            return string.Format("{0} {1}", FirstName, LastName);
        }
    }
}

还有一个简单的查询:

using (var cn = new SqlConnection("..."))
{
    var usersRepository = new DapperRepository<User>(cn)
    var allUsers = await userRepository.FindAllAsync(x => x.AccountId == 3 && x.Status != UserStatus.Deleted);
}

也许对你有用呢?

MicroOrm.Dapper.Repositories


6
我理解你的回答并不是对原问题的回答,因为你没有使用 LINQ。然而,你的解决方案是任何人在不使用 Entity Framework 的情况下获得类型安全、跨进程查询表达式的最接近方式。太棒了!干得好! - Brent Arias
这个看起来是一个有效的答案。实际上,你可以输入 var result = await new DapperRepository<TModel>(cn).FindAllAsync(query).ToListAsync(),这正是他想要的。 - Antonio Rodríguez

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