如何将 Dapper 与条件 StringBuilder 查询一起使用?

How to use Dapper with conditional StringBuilder query?

我正在尝试构建一个动态查询字符串并将其应用于 dapper(以某种方式提供了与 Entity Framework 可选“.include(x=>x.XXX)”功能类似的 "feel" ):

构建查询字符串

var query = new StringBuilder();
query.Append(" select * from test_post p ");
if (validIncludesToPerform[0]) 
              query.Append(" left join sys_user u on u.Id = p.CreatorId ");
if (validIncludesToPerform[1]) 
              query.Append(" left join test_poststatus s on s.Id = p.StatusId ");
query.Append(" where p.Id = @Id; ");

var isQueryMultiple = false;
if (validIncludesToPerform[2])
{
    isQueryMultiple = true;
    query.Append(" select * from test_postnote n where n.PostId = @Id; ");
}
if (validIncludesToPerform[3])
{
    isQueryMultiple = true;
    query.Append(@" select * 
                   from test_post_tag pt 
                   left join test_tag t on t.Id = pt.TagId 
                   where pt.PostId = @Id; ");
}

处理查询

Post pst;
if (isQueryMultiple)
{
    using (var multi = Connection.QueryMultiple(query.ToString(), new { Id = id }))
    {
        pst = multi.Read<Post, User, PostStatus, Post>((post, user, status) =>
        {
            if (post == null) return null;
            if (user != null) post.Creator = user;
            if (status != null) post.Status = status;
            return post;
        }).FirstOrDefault();

        if (pst != null && validIncludesToPerform[2])
            pst.Notes = multi.Read<PostNote>().ToList();

        if (pst != null && validIncludesToPerform[3])
            pst.PostTags = multi.Read<PostTag, Tag, PostTag>((pTag, tag) =>
                                    {
                                        if (pTag == null) return null;
                                        if (tag != null) pTag.Tag = tag;
                                        return pTag;
                                    }).ToList();
    }
}
else
{
    pst = Connection.Query<Post, User, PostStatus, Post>
                        (query.ToString(), (post, user, status) =>
                        {
                            if (post == null) return null;
                            if (user != null) post.Creator = user;
                            if (status != null) post.Status = status;
                            return post;
                        }, new { Id = id }).FirstOrDefault();
}

重要说明: Dapper 假定您的 Id 列被命名为 "Id" 或 "id",如果您的主键不同或您想拆分"Id" 以外的点的宽行,使用可选的 'splitOn' 参数。这意味着在我的例子中我不必指定 "Id,Id" 因为这部分已经是隐式的。

错误信息: "When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id\r\nParameter name: splitOn"

原因:泛型的使用意味着查询将接收“3 个部分”,但它只是接收到一个较小的数字....

丑陋的解决方案: 我知道如果我只是将 StatusUser 分成每个独立的查询,这会起作用:

if (validIncludesToPerform[0])
   query.Append(@" select u.* from sys_user u right join test_post p on u.Id = p.CreatorId 
                   where p.Id = @Id;");
if (validIncludesToPerform[1])
   query.Append(@" select ps.* from test_poststatus ps right join test_post p on ps.Id = p.StatusId 
                   where p.Id = @Id;");

但由于此信息是一对一的关系,我认为单个查询更好。所以,我尽量避免使用这个解决方案。

我也不想列出泛型签名的所有可能组合,例如:

一旦我开始向这个 class 添加更多 1to1 关系,这种方法就不友好了。

我该如何解决这个问题?

您需要告诉 dapper 在何处拆分 - 对象之间的边界在您的 select statement.That 中是您的错误声明所说的位置。因此,将您的电话修改为:

pst = multi.Read<Post, User, PostStatus, Post>((post, user, status) =>
{
   if (post == null) return null;
   if (user != null) post.Creator = user;
   if (status != null) post.Status = status;
   return post;
}, splitOn: "Id,Id").FirstOrDefault();

split on 告诉 Dapper 在何处停止映射到一个对象并开始映射到另一个对象。由于您有 2 个联接,因此您需要拆分两个字段。确保将这两个放在一个由逗号分隔的字符串中,并且字符串中没有 space 个字符...

如需更好的解释,请参阅此 post:

Correct use of Multimapping in Dapper

我找到了一个 "somehow" 不太糟糕的方法:

        var query = new StringBuilder();
        query.Append(" select * from test_post p ");

        query.Append(validIncludesToPerform[0]
            ? " left join sys_user u on u.Id = p.CreatorId "
            : " left join sys_user u on 1 = 0 ");

        query.Append(validIncludesToPerform[1]
            ? " left join test_poststatus s on s.Id = p.StatusId "
            : " left join test_poststatus s on 1 = 0 ");

        query.Append(" where p.Id = @Id; ");

我暂时留下这个答案,等待更好的选择....