使用Dapper.net进行多重映射查询时出现意外行为

5

我刚开始研究Dapper.net并尝试使用一些不同的查询,其中一个查询产生了奇怪的结果,让我感到意外。

我有两张表 - PhotosPhotoCategories,它们之间通过CategoryID进行关联。

Photos表

PhotoId (PK - int)  
CategoryId (FK - smallint)  
UserId (int)

照片分类表

CategoryId (PK - smallint)  
CategoryName (nvarchar(50))

我的两个类:

public class Photo
{
    public int PhotoId { get; set; }
    public short CategoryId { get; set; }
    public int UserId { get; set; }
    public PhotoCategory PhotoCategory { get; set; }
}

public class PhotoCategory
{
    public short CategoryId { get; set; }
    public string CategoryName { get; set; }
{

我希望使用多重映射来返回一个Photo实例,并填充相关的PhotoCategory实例。
var sql = @"select p.*, c.* from Photos p inner 
            join PhotoCategories c 
            on p.CategoryID = c.CategoryID where p.PhotoID = @pid";

cn.Open();
var myPhoto = cn.Query<Photo, PhotoCategory, Photo>(sql, 
               (photo, photoCategory) => { photo.PhotoCategory = photoCategory; 
                                           return photo; }, 
               new { pid = photoID }, null, true, splitOn: "CategoryID").Single();

当执行此代码时,尽管数据库表和对象中具有相同的名称,但并非所有属性都被填充。
我注意到如果在我的SQL中不选择“select p.*等”,而是明确指定字段,则会填充所有内容。
我想从查询结果中排除CategoryId,然后所有其他内容都会被填充(当然,我已经在select语句中排除了Photo对象的CategoryId)。
但我希望能够在查询中包含该字段,并且查询中的所有其他字段以及CategoryId都能被填充。
我可以将Photo类中的CategoryId属性排除在外,每当需要ID时都使用Photo.PhotoCategory.CategoryId。
但在某些情况下,当我获取Photo对象实例时,可能不想填充PhotoCategory对象。
有人知道为什么会出现上述行为吗?这对于Dapper来说正常吗?

...但在某些情况下,我可能不想填充PhotoCategory对象...在这种情况下,您当然可以始终创建虚拟的PhotoCategory对象并设置它们的Id。 当我只需要传递标识符时但我的方法需要对象实例时,我经常这样做。 - Robert Koritnik
为什么不在Dapper的GitHub存储库上**开一个问题(issue)**呢?这样更有可能更快地得到服务,并且项目所有者将记录该问题。因为这似乎很常见。我的自己的表通常都与相同的列名相关联。 - Robert Koritnik
可能是[Dapper multiselect:嵌套类主键不映射,除非使用“AS”]的重复问题(https://dev59.com/M1nUa4cB1Zd3GeqPY0Vf)。 - Robert Koritnik
3个回答

5
我刚刚修复了这个问题:
    class Foo1 
    {
        public int Id;
        public int BarId { get; set; }
    }

    class Bar1
    {
        public int BarId;
        public string Name { get; set; }
    }

    public void TestMultiMapperIsNotConfusedWithUnorderedCols()
    {

        var result = connection.Query<Foo1,Bar1,
                      Tuple<Foo1,Bar1>>(
                         "select 1 as Id, 2 as BarId, 3 as BarId, 'a' as Name",
                         (f,b) => Tuple.Create(f,b), splitOn: "BarId")
                         .First();

        result.Item1.Id.IsEqualTo(1);
        result.Item1.BarId.IsEqualTo(2);
        result.Item2.BarId.IsEqualTo(3);
        result.Item2.Name.IsEqualTo("a");

    }

多重映射器会因为在first类型中有一个字段,也出现在second类型中...并且...被用作分割点而感到困惑。
现在,为了克服这个问题,Dapper允许Id字段在第一个类型的任何位置出现。例如:
假设我们有:
classes: A{Id,FooId} B{FooId,Name}
splitOn: "FooId"
data: Id, FooId, FooId, Name
旧的拆分方法没有考虑它正在映射的实际基础类型。所以...它映射了Id => AFooId, FooId, Name => B新方法知道A中的属性和字段。当它在流中首次遇到FooId时,它不会开始拆分,因为它知道A有一个名为FooId的属性需要映射。下一次看到FooId时,它将进行拆分,从而得到预期的结果。

嗨,Sam。太好了,谢谢。这个更新版本什么时候会出现在 https://github.com/SamSaffron/dapper-dot-net/commits/master 上?我目前还看不到它? - marcusstarnes
谢谢Sam,这解决了一切 :) - marcusstarnes
就像我当时想的那样。感谢修复。 - Mr Grok

0

我遇到了类似的问题。这与子元素和父元素都使用相同的字段名称进行拆分有关。例如,以下代码可以正常工作:

class Program
{
    static void Main(string[] args)
    {
        var createSql = @"
            create table #Users (UserId int, Name varchar(20))
            create table #Posts (Id int, OwnerId int, Content varchar(20))

            insert #Users values(99, 'Sam')
            insert #Users values(2, 'I am')

            insert #Posts values(1, 99, 'Sams Post1')
            insert #Posts values(2, 99, 'Sams Post2')
            insert #Posts values(3, null, 'no ones post')
        ";

        var sql =
        @"select * from #Posts p 
        left join #Users u on u.UserId = p.OwnerId 
        Order by p.Id";

        using (var connection = new SqlConnection(@"CONNECTION STRING HERE"))
        {
            connection.Open();
            connection.Execute(createSql);

            var data = connection.Query<Post, User, Post>(sql, (post, user) => { post.Owner = user; return post; }, splitOn: "UserId");
            var apost = data.First();

            apost.Content = apost.Content;
            connection.Execute("drop table #Users drop table #Posts");
        }
    }
}

class User
{
    public int UserId { get; set; }
    public string Name { get; set; }
}
class Post
{
    public int Id { get; set; }
    public int OwnerId { get; set; }
    public User Owner { get; set; }
    public string Content { get; set; }
}

但是以下代码不行,因为"UserId"在两个表格和两个对象中都被使用了。

class Program
{
    static void Main(string[] args)
    {
        var createSql = @"
            create table #Users (UserId int, Name varchar(20))
            create table #Posts (Id int, UserId int, Content varchar(20))

            insert #Users values(99, 'Sam')
            insert #Users values(2, 'I am')

            insert #Posts values(1, 99, 'Sams Post1')
            insert #Posts values(2, 99, 'Sams Post2')
            insert #Posts values(3, null, 'no ones post')
        ";

        var sql =
        @"select * from #Posts p 
        left join #Users u on u.UserId = p.UserId 
        Order by p.Id";

        using (var connection = new SqlConnection(@"CONNECTION STRING HERE"))
        {
            connection.Open();
            connection.Execute(createSql);

            var data = connection.Query<Post, User, Post>(sql, (post, user) => { post.Owner = user; return post; }, splitOn: "UserId");
            var apost = data.First();

            apost.Content = apost.Content;
            connection.Execute("drop table #Users drop table #Posts");
        }
    }
}

class User
{
    public int UserId { get; set; }
    public string Name { get; set; }
}
class Post
{
    public int Id { get; set; }
    public int UserId { get; set; }
    public User Owner { get; set; }
    public string Content { get; set; }
}

Dapper映射在这种情况下似乎非常混乱。这描述了问题,但除了面向对象的设计决策之外,我们是否有解决方案/解决方法?

0

我知道这个问题很老,但是我想为某人节省2分钟,给出一个显而易见的答案:只需从一个表中别名一个id:

例如:

SELECT
    user.Name, user.Email, user.AddressId As id, address.*
FROM 
    User user
    Join Address address
    ON user.AddressId = address.AddressId

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