我不理解dapper的映射、multimapping和QueryMultiple

本文关键字:multimapping QueryMultiple 映射 不理解 dapper | 更新日期: 2023-09-27 18:02:09

标题说明了一切,我试图使用它,但我不明白它。这个问题可能是由于我是一个业余爱好者而缺乏知识,但是我已经读了很多关于这个东西的问题,并且在谷歌上搜索了三天,我仍然不理解它。

我有这么多的问题,我不确定我应该写在一个问题,甚至有人会读它所有。如果有人有其他的解决方案,或者认为我应该把它分成不同的问题……好吧,我愿意听取建议。

我打算写一个例子,但是我又读了好几天的例子,没有帮助我。

我只是不能让我的头脑去理解像github的例子是如何工作的:

var sql = 
@"select * from #Posts p 
left join #Users u on u.Id = p.OwnerId 
Order by p.Id";
var data = connection.Query<Post, User, Post>(sql, (post, user) => { post.Owner = user; return post;});

那么,Post有一个类型为User的属性这个属性叫做Owner,对吧?比如:

public class Post
{
    ...
    public User Owner { get; set;}
}

因此Query<Post, User, Post>将返回具有所有属性的Post实例,并且将创建User实例并将其分配给Post.Owner属性?如何将简单的参数添加到该查询中,例如,如果有人想将id作为int参数传递给...WHERE Id = @Id", new {Id = id},那么给定参数现在是(post, user) => { post.Owner = user; return post;},该参数应该添加到哪里?参数总是引用给定的类型,只能使用简单的典型参数进行动态查询,两者可以同时使用吗?如何?

另外,如何区分哪个DB字段属于哪个对象?它让类名=DB表名?如果类没有与DB表相同的名称,我想使用[Table]属性,它会起作用还是属性仅适用于Dapper.Contrib.Extensions方法?它是否适用于共享同一DB表的对象?

关于同一表不同对象的问题,假设我有一个Person对象它有一个BankAccount对象:

public class Person
{
    ...
    public BankAccount Account {get; set;}
    ...
}
public class BankAccount
{
    private string _Account;
    public string Account
    {
        get { return _Account; }
        set
        {
            if(!CheckIfIBANIsCorrect(value))
                throw new Exception();
            _Account = value;
        }
    }
    private bool CheckIfIBANIsCorrect(string IBAN)
    {
        //...
        //Check it
    }
}

我可以将字符串account存储在与Person相同的表中,因为每个人都有一个由该人的Id引用的单个帐户。我该如何映射这样的东西呢?是否有一种方法,我应该简单地加载结果在一个动态对象,然后创建所有的对象,Query将创建Person对象的其余部分,我应该自己创建嵌套的对象?

顺便说一下,splitOn应该如何在所有这些中使用?我知道它应该把结果分成各种"组",这样你就可以按id拆分结果,并采取你需要的东西,但我不明白我应该如何从不同的"组"中检索信息,以及它如何返回不同的"组",列表,枚举,什么?

QueryMultiple是另一件远远超出我理解的事情,不管我读了多少问题和答案。你知道的….Read的东西是如何工作的?我在这里读到的或在谷歌上搜索到的所有内容都假设Read是某种自动的东西,可以奇迹般地区分物体。它是否按类名划分结果这样我就能确保每个对象都有正确的表名?在这种情况下,[Table]属性会发生什么?

我想我的问题是我找不到(我想它不存在)一个web页面,描述这一切在GitHub非常稀缺(例子),以及在具体的案件中我仍然只有找到答案,没有回答什么我试着去了解但只有,具体的情况下,这是令人困惑的我当我读它们的时候越来越多,因为似乎每个人都使用不同的方法没有解释为什么或怎样。

我不理解dapper的映射、multimapping和QueryMultiple

我认为您的主要问题与连接表查询的Dapper查询是认为列表中的第二个参数始终是"参数"参数。考虑下面的代码:

var productsWithoutCategories = conn.Query<Product>(
    "SELECT * FROM Products WHERE ProductName LIKE @nameStartsWith + '%'",
    new { nameStartsWith = "a" }
);

这里,有两个参数"sql"answers"param" -如果我们使用命名参数,那么代码将是这样的:

var productsWithoutCategories = conn.Query<Product>(
    sql: "SELECT * FROM Products WHERE ProductName LIKE @nameStartsWith + '%'",
    param: new { nameStartsWith = "a" }
);

在你的例子中,你有

var data = connection.Query<Post, User, Post>(sql, (post, user) => { post.Owner = user; return post;});

第二个参数实际上是一个名为"map"的参数,它告诉Dapper如何在SQL查询中连接两个表的情况下组合实体。如果我们使用命名参数,那么它看起来像这样:

var data = connection.Query<Post, User, Post>(
    sql: sql,
    map: (post, user) => { post.Owner = user; return post;}
);

我将在一个完整的示例中使用northnd数据库类。假设我们有类

public class Product
{
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    public bool Discontinued { get; set; }
    public Category Category { get; set; }
}
public class Category
{
    public int CategoryID { get; set; }
    public string CategoryName { get; set; }
}

并且我们想要构建一个product列表,并填充嵌套的Category类型,我们将执行以下操作:

using (var conn = new SqlConnection("Server=.;Database=NORTHWND;Trusted_Connection=True;"))
{
    var productsWithCategories = conn.Query<Product, Category, Product>(
        "SELECT * FROM Products INNER JOIN Categories ON Categories.CategoryID = Products.CategoryID,
        map: (product, category) =>
        {
            product.Category = category;
            return product;
        },
        splitOn: "CategoryID"
    );
}

这将遍历JOIN后的产品和类别数据的所有行,并生成一个唯一的产品列表,但无法确定如何将类别数据与之组合,因此它需要一个"map"函数,该函数接受一个产品实例和一个类别实例,并且必须返回一个将类别数据与之组合的产品实例。在本例中,这很简单——我们只需要将Product实例上的Category属性设置为Category实例。

注意,我必须指定一个"splitOn"值。Dapper假定表的关键列将被简单地称为"Id",如果是,那么它可以自动处理这些列上的连接。然而,在本例中,我们连接了一个名为"CategoryID"的列,因此我们必须告诉Dapper根据该列名将数据拆分(分为Products和Categories)。

如果我们还想指定"param"对象来过滤结果,那么我们可以做如下操作:

var productsWithCategories = conn.Query<Product, Category, Product>(
    "SELECT * FROM Products INNER JOIN Categories ON Categories.CategoryID = Products.CategoryID WHERE ProductName LIKE @nameStartsWith + '%'",
    map: (product, category) =>
    {
        product.Category = category;
        return product;
    },
    param: new { nameStartsWith = "a" },
    splitOn: "CategoryID"
);

为了回答你的最后一个问题,QueryMultiple简单地一次执行多个查询,然后允许你分别读取它们。例如,与其这样做(使用两个单独的查询):

using (var conn = new SqlConnection("Server=.;Database=NORTHWND;Trusted_Connection=True;"))
{
    var categories = conn.Query("SELECT * FROM Categories");
    var products = conn.Query("SELECT * FROM Products");
}

您可以指定一个SQL语句,在一个批处理中包含两个查询,但是您需要从QueryMultiple:

返回的组合结果集中分别读取它们:
using (var conn = new SqlConnection("Server=.;Database=NORTHWND;Trusted_Connection=True;"))
{
    var combinedResults = conn.QueryMultiple("SELECT * FROM Categories; SELECT * FROM Products");
    var categories = combinedResults.Read<Category>();
    var products = combinedResults.Read<Product>();
}

我认为我看到的QueryMultiple的其他例子有点令人困惑,因为它们通常从每个查询返回单个值,而不是完整的行集(这是在简单的查询调用中更常见的)。希望以上内容能帮你解决这个问题。

注意:我还没有涵盖你关于[Table]属性的问题-如果你在尝试了这个之后仍然有问题,那么我建议为它创建一个新的问题。Dapper使用"splitOn"值来决定一个实体的列何时结束,何时开始(在上面的JOIN示例中,有Product字段,然后是Category字段)。如果您将Category类重命名为其他东西,那么查询仍然可以工作,Dapper在这种情况下不依赖于表名-因此希望您根本不需要[table]。