使用 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();
}
}
如何更改此设置,以便在创建对象时考虑 MediaItem
的 tag
属性,对于这两种情况(通过 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();
考虑一个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();
}
}
如何更改此设置,以便在创建对象时考虑 MediaItem
的 tag
属性,对于这两种情况(通过 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();