使用 Dapper 构建具有多对多关系的对象

Building objects with many-to-many relationship using Dapper

考虑一个Sqlite数据库,其部分模式如下所示(我们这里不考虑Book_Tag table)。使用 link table Media_Tag 注意媒体项和标签之间的多对多关系:

这些table的对象模型如下:

public enum MediaType
{
    Dvd,
    BluRay,
    Cd,
    Vhs,
    Vinyl,
    Other
}

public class MediaItem 
{
    public MediaType type { get; set; }
    public long number { get; set; } 
    public int runningTime { get; set; }
    public int releaseYear { get; set; }

    public ICollection<Tag> tags { get; set; }
}

public class Tag 
{
    public string name { get; set; }
}

目前,Dapper 正用于从媒体中读取 table,但未考虑标签。代码如下:

public IEnumerable<MediaItem> readAll()
{
    using (var db = new SqliteConnection(this.connectionString))
    {
        db.Open();

        var sql = "SELECT * FROM Media;";
        return db.Query<MediaItem>(sql);
    }
}

public MediaItem readById(int id)
{
    using (var db = new SqliteConnection(this.connectionString))
    {
        db.Open();

        var sql = "SELECT * FROM Media WHERE id = @id;";
        var @params = new { id = id };
        return db.Query<MediaItem>(sql, @params).First();
    }
}

如何更改此设置,以便在创建对象时考虑 MediaItemtag 属性,对于这两种情况(通过 id 读取并从 table)?是否需要连接查询?我确信 Dapper 有办法很好地做到这一点,但我不知道它是如何完成的。

您对 link table 中的任何内容都不感兴趣,因此 SQL 应该这样做:

SELECT M.Id, M.title, M.type, M.Number, M.image, M.runningTime, M.releaseYear, T.Id, T.Name FROM Media as M 
INNER JOIN Media_Tag AS MT ON M.id = MT.mediaId
INNER JOIN Tags AS T ON T.id = MT.tagId

如果 SqLite 允许,您可以使用 M.*, T.*

我冒昧地将 Id 属性添加到您的实体 类。我想你会需要它,否则你所有的标签都会不同而不是唯一的。没有它你可能也能正常工作,但它应该会让你的生活更轻松。

public class MediaItem 
{
    public int Id { get; set; } // New
    public MediaType type { get; set; }
    public long number { get; set; } 
    public int runningTime { get; set; }
    public int releaseYear { get; set; }

    public ICollection<Tag> tags { get; set; }
}

public class Tag 
{
    public int Id { get; set; } // New
    public string name { get; set; }
}

由于您的实体 类 都有唯一的 ID,因此您必须选择它们并确保它们在整个结果中都是唯一的。我们通过使用字典来保存它们来做到这一点。我只显示 ReadAll,您应该可以相应地执行 ReadById。

string sql = "<see above>";

using (var db = new SqliteConnection(this.connectionString))
{            
    var mediaDictionary = new Dictionary<int, Media>();
    var tagDictionary = new Dictionary<int, Tag>();

    var list = db.Query<Media, Tag, Media>(
    sql,
    (media, tag) =>
    {
        Media mediaEntry;

        if (!mediaDictionary.TryGetValue(media.Id, out mediaEntry))
        {
            // Haven't seen that one before, let's add it to the dictionary
            mediaEntry = media;
            mediaDictionary.Add(mediaEntry.Id, mediaEntry);
        }

        Tag tagEntry;

        if (!tagDictionary.TryGetValue(tag.Id, out tagEntry))
        {
            // Haven't seen that one before, let's add it to the dictionary
            tagEntry = tag;
            tagDictionary.Add(tagEntry.Id, tagEntry);
        }

        // Add the tag to the collection
        mediaEntry.Tags.Add(tagEntry);
        return mediaEntry;
    },
    splitOn: "Id") // This default and could be omitted
    .Distinct()
    .ToList();