标签(Tag)的各种设计方案
標(biāo)簽(Tag)的各種設(shè)計(jì)方案
首先,標(biāo)簽(Tag)是什么?
我的理解:用來具體區(qū)分某一類內(nèi)容的標(biāo)識(shí),和標(biāo)簽類似的一個(gè)概念是分類(Category),有一個(gè)示例可以很好的區(qū)分它們兩個(gè),比如人類分為:白種人、黃種人和黑種人(可以看作分類),但又可以根據(jù)職業(yè)分為:農(nóng)民、工人和程序猿等等(可以看作標(biāo)簽)。
概括來說就是:分類是大而全的概念(用抽象概念來區(qū)分),標(biāo)簽是小而具體的概念(用具體值來區(qū)分)。
在所有的 CMS 內(nèi)容管理系統(tǒng)中(比如園子中的博客、博問、新聞、網(wǎng)摘等),都存在標(biāo)簽(Tag)的概念,而且它們大多都有相似的功能,對(duì)于這類問題,我們最好把它抽象出來,然后單獨(dú)去考慮并探討它,如果一切順利的話,最后完成的東西就是標(biāo)簽領(lǐng)域(TagDomain),當(dāng)然這是最理想的方式。
我們先從實(shí)際應(yīng)用出發(fā),今天要探討的是:各種標(biāo)簽(Tag)模型設(shè)計(jì)下,各類應(yīng)用操作的實(shí)現(xiàn)方式。
標(biāo)簽(Tag)模型,我大概設(shè)計(jì)了 4 種(也可以在這個(gè)基礎(chǔ)上進(jìn)行擴(kuò)展),如下:
1. Tag 存在于 Post 中。
2. Tag 獨(dú)立 Post(一對(duì)多關(guān)系)。
3. Tag 獨(dú)立 Post(一對(duì)多關(guān)系),Post 中多一個(gè) Tags。
4. Tag 和 Post 都獨(dú)立,創(chuàng)建 TagMap 映射(多對(duì)多關(guān)系)。
應(yīng)用操作(EF Linq 實(shí)現(xiàn)),我大概想了 8 種,對(duì)于 Tag 的一般操作,我想應(yīng)該都包含了,如下:
1. 添加 Post-Tag
2. 單獨(dú)修改 Tag
3. 在 Post 中修改 Tag
4. 單獨(dú)刪除 Tag
5. 在 Post 中刪除Tag
6. 查詢 Tag(帶數(shù)量統(tǒng)計(jì))
7. 查詢 Post(Tag 展示)
8. 根據(jù) Tag 查詢 Post 列表
下面我們分別來探討下。
1. Tag 存在于 Post 中。
Tag 模型圖:
Tag 模型說明:這個(gè) Tag 模型是最簡單的,Tag 直接存在 Post 中,但是模型簡單,就意味著應(yīng)用操作實(shí)現(xiàn)會(huì)很復(fù)雜。
應(yīng)用操作實(shí)現(xiàn)代碼:
public void Tags1()
{
using (var context = new TagsDbContext())
{
//1.添加post-tag
var postAdd = new Post1 { UserId = 1, Title = "title", Content = "content", Tags = ".net|asp.net vnext" };
context.Post1s.Add(postAdd);
//2.4.6單獨(dú)對(duì)tag進(jìn)行修改、刪除、查詢(帶數(shù)量統(tǒng)計(jì)),難于登天。。。
//3.在post中修改tag
var postModify = context.Post1s.FirstOrDefault(p => p.PostId == 1 && p.UserId == 1);
postModify.Tags.Replace("asp.net vnext", "asp.net5");
//5.在post中刪除tag
var postTagDelete = context.Post1s.FirstOrDefault(p => p.PostId == 1 && p.UserId == 1);
postTagDelete.Tags.Replace("asp.net vnext", "");
//7.查詢post(tag展示)
var postSelect = context.Post1s.FirstOrDefault(p => p.PostId == 1 && p.UserId == 1);
postSelect.Tags.Replace('|', ',');
//8.根據(jù)tag查詢post
var postTagSelect = context.Post1s.Where(p => p.Tags.Contains("asp.net5") && p.UserId == 1).ToList();
context.SaveChanges();
}
}
結(jié)論:可以看到,對(duì)于 2.4.6 應(yīng)用操作,這種模型根本就沒辦法進(jìn)行操作(也可以,但實(shí)現(xiàn)起來太復(fù)雜),2.4.6 應(yīng)用操作屬于對(duì) Tag 的單獨(dú)操作,如果應(yīng)用場景只要求在 Post 中進(jìn)行 Tag 操作,這種模型是完全可以勝任的,但如果要對(duì) Tag 進(jìn)行單獨(dú)操作,用這種 Tag 模型,那就是自殺行為。。。
2. Tag 獨(dú)立 Post(一對(duì)多關(guān)系)。
Tag 模型圖:
Tag 模型說明:這種設(shè)計(jì)雖然把 Tag 和 Post 分離了,但需要注意的是,Post 和 Tag 的關(guān)系是一對(duì)多,有人會(huì)說,Post 和 Tag 的關(guān)系不是多對(duì)多的嗎?一個(gè) Tag 也可能對(duì)應(yīng)多個(gè) Post,但這種模型設(shè)計(jì)并不是這樣,Tag 中有一個(gè) PostId,表示這個(gè) Tag 屬于哪個(gè) Post,比如有這樣的示例:Tag 為 ASP.NET 的 Post 有兩篇,那么在 Tag 中就會(huì)有兩條 Tag 為 ASP.NET 的數(shù)據(jù),但對(duì)應(yīng)不同的 PostId。
應(yīng)用操作實(shí)現(xiàn)代碼:
public void Tags2()
{
using (var context = new TagsDbContext())
{
//1.添加post-tag
var postAdd = new Post2 { UserId = 1, Title = "title", Content = "content" };
postAdd.Tag2s.Add(new Tag2 { UserId = 1, TagName = ".net" });
postAdd.Tag2s.Add(new Tag2 { UserId = 1, TagName = "asp.net vnext" });
context.Post2s.Add(postAdd);
//2.單獨(dú)修改tag
var tagsModify = context.Tag2s.Where(t => t.TagName == "asp.net vnext" && t.UserId == 1).ToList();
tagsModify.ForEach(t => t.TagName = "asp.net5");
//3.在post中修改tag
var tagModify = context.Tag2s.FirstOrDefault(t => t.TagName == "asp.net vnext" && t.UserId == 1 && t.PostId == 1);
tagModify.TagName = "asp.net5";
//4.單獨(dú)刪除tag
var tagsDelete = context.Tag2s.Where(t => t.TagName == "asp.net vnext" && t.UserId == 1).ToList();
context.Tag2s.RemoveRange(tagsDelete);
//5.在post中刪除tag
var tagDelete = context.Tag2s.FirstOrDefault(t => t.TagName == "asp.net vnext" && t.UserId == 1 && t.PostId == 1);
context.Tag2s.Remove(tagDelete);
//6.查詢tag(帶數(shù)量統(tǒng)計(jì))
var tagsSelect = from t in context.Tag2s
where t.UserId == 1
group t by t.TagName into g
orderby g.Count() descending
select new
{
TagName = g.Key,
UseCount = g.Count()
};
//7.查詢post(tag展示)
var postSelect = context.Post2s.Include(p => p.Tag2s).FirstOrDefault(p => p.PostId == 1 && p.UserId == 1);
var tags = string.Join(",", postSelect.Tag2s.Select(t => t.TagName));
//8.根據(jù)tag查詢post
var postTagSelect = from p in context.Post2s
join t in context.Tag2s on p.PostId equals t.PostId
where t.TagName == "asp.net5" && p.UserId == 1
select p;
context.SaveChanges();
}
}
結(jié)論:可以看到,使用這種 Tag 模型,7種應(yīng)用操作的實(shí)現(xiàn)都不是很復(fù)雜,但有一個(gè)缺點(diǎn)是:Tag 重復(fù)數(shù)據(jù)會(huì)很多,如果有 10 個(gè) Post,每個(gè) Post 有 3 個(gè) Tag,不管 Tag 是否相同,那么 Tag 的數(shù)據(jù)就是 30 條。如果對(duì)于數(shù)據(jù)量要求不大的話,可以采用這種方式,畢竟實(shí)現(xiàn)起來不是很復(fù)雜(比如其他三種的實(shí)現(xiàn)),我個(gè)人也比較偏向這種 Tag 模型設(shè)計(jì)。
3. Tag 獨(dú)立 Post(一對(duì)多關(guān)系),Post 中多一個(gè) Tags。
Tag 模型說明:這種模型設(shè)計(jì)和上面第二種差不多,只不過在 Post 中多了個(gè) Tags(String 類型),它的作用就是為了在 Post Tag 展示的時(shí)候,不用再去關(guān)聯(lián)查找 Tag,方便是方便,但我們需要付出一些代碼,那就是需要對(duì) Post 中的 Tags 進(jìn)行維護(hù),利與弊,我們看下應(yīng)用操作的實(shí)現(xiàn),就知道了。
應(yīng)用操作實(shí)現(xiàn)代碼:
public void Tags3()
{
using (var context = new TagsDbContext())
{
//1.添加post-tag
var postAdd = new Post3 { UserId = 1, Title = "title", Content = "content", Tags = ".net|asp.net vnext" };
context.Post3s.Add(postAdd);
context.Tag3s.Add(new Tag3 { UserId = 1, TagName = ".net" });
context.Tag3s.Add(new Tag3 { UserId = 1, TagName = "asp.net vnext" });
//2.單獨(dú)修改tag
var tagsModify = context.Tag3s.Where(t => t.TagName == "asp.net vnext" && t.UserId == 1).ToList();
var postsModify = (from p in context.Post3s
join t in tagsModify on p.PostId equals t.PostId
select p).ToList();
tagsModify.ForEach(t => t.TagName = "asp.net5");
postsModify.ForEach(p => p.Tags.Replace("asp.net vnext", "asp.net5"));
//3.在post中修改tag
var tagModify = context.Tag3s.FirstOrDefault(t => t.TagName == "asp.net vnext" && t.UserId == 1 && t.PostId == 1);
tagModify.TagName = "asp.net5";
var postModify = context.Post3s.FirstOrDefault(t => t.UserId == 1 && t.PostId == 1);
postModify.Tags.Replace("asp.net vnext", "asp.net5");
//4.單獨(dú)刪除tag
var tagsDelete = context.Tag3s.Where(t => t.TagName == "asp.net vnext" && t.UserId == 1).ToList();
var postsTagsModify = (from p in context.Post3s
join t in tagsModify on p.PostId equals t.PostId
select p).ToList();
context.Tag3s.RemoveRange(tagsDelete);
postsModify.ForEach(p => p.Tags.Replace("asp.net vnext", ""));
//5.在post中刪除tag
var tagDelete = context.Tag3s.FirstOrDefault(t => t.TagName == "asp.net vnext" && t.UserId == 1 && t.PostId == 1);
context.Tag3s.Remove(tagDelete);
var postTagDelete = context.Post3s.FirstOrDefault(t => t.UserId == 1 && t.PostId == 1);
postTagDelete.Tags.Replace("asp.net vnext", "");
//6.查詢tag(帶數(shù)量統(tǒng)計(jì))
var tagsSelect = from t in context.Tag3s
where t.UserId == 1
group t by t.TagName into g
orderby g.Count() descending
select new
{
TagName = g.Key,
UseCount = g.Count()
};
//7.查詢post(tag展示)
var postSelect = context.Post3s.FirstOrDefault(p => p.PostId == 1 && p.UserId == 1);
postSelect.Tags.Replace("|", ",");
//8.根據(jù)tag查詢post
var postTagSelect = from p in context.Post3s
join t in context.Tag3s on p.PostId equals t.PostId
where t.TagName == "asp.net5" && p.UserId == 1
select p;
context.SaveChanges();
}
}
結(jié)論:先不看應(yīng)用操作的具體實(shí)現(xiàn),單純從代碼量上和第二種進(jìn)行對(duì)比,會(huì)發(fā)現(xiàn)這種 Tag 模型的應(yīng)用操作實(shí)現(xiàn)代碼會(huì)比較多,添加、修改和刪除 Tag,都要對(duì) Post 中的 Tags 進(jìn)行操作,而我們做這些多余的工作,僅僅是換來的是,最后查詢 Post 而不關(guān)聯(lián) Tag,總感覺有點(diǎn)得不償失,但并不意味著這種 Tag 模型實(shí)現(xiàn)就無用武之地,如果我們的應(yīng)用場景,要求對(duì) Tag 操作,必須通過 Post,比如修改 Tag,則必須通過 Post 進(jìn)行修改,也就是不能對(duì) Tag 進(jìn)行獨(dú)立操作,那么這種 Tag 模型就很適用。
應(yīng)用場景不要求對(duì) Tag 進(jìn)行獨(dú)立操作,上面說到,第一種 Tag 模型設(shè)計(jì)也適用啊,它們有什么不同呢? 分離 Tag 的好處是什么呢?很簡單,就是為了方便 Tag 使用數(shù)量的統(tǒng)計(jì),如果應(yīng)用場景要求這個(gè)操作,第一種 Tag 模型設(shè)計(jì)就不適用了。
4. Tag 和 Post 都獨(dú)立,創(chuàng)建 TagMap 映射(多對(duì)多關(guān)系)。
Tag 模型說明:這種 Tag 模型和上面第二種形成鮮明對(duì)比,上面第二種 Post 和 Tag 是一對(duì)多關(guān)系,而這種是多對(duì)多關(guān)系,第二種會(huì)出現(xiàn)重復(fù) Tag 數(shù)據(jù),而這種則不會(huì)。從模型圖中,我們可以看到,Post 和 Tag 是獨(dú)立存在的,它們通過一個(gè) TagMap 進(jìn)行映射關(guān)聯(lián),Tag 中的 UserId 和 TagName 是唯一的,并且多了一個(gè) UseCount,在第二種 Tag 模型中,因?yàn)?Tag 根據(jù) Post 產(chǎn)生,我們想要統(tǒng)計(jì) Tag 的使用數(shù)量,直接對(duì) Tag 進(jìn)行 GroupBy 就可以了(具體看第二種的實(shí)現(xiàn)代碼),而這種 Tag 模型,某一特定用戶下的 Tag 是唯一的,所以要想統(tǒng)計(jì) Tag 的使用數(shù)量,就必須通過 TagMap(需要關(guān)聯(lián) Tag 實(shí)現(xiàn)),既然 Tag 是獨(dú)立的,那還不如增加一個(gè) UseCount 更加方便。
這種 Tag 模型設(shè)計(jì)是四種方案中最復(fù)雜的,好處就是模型更加健壯,方便擴(kuò)展,沒有榮冗余數(shù)據(jù)產(chǎn)生,那壞處呢?我們接著看下面。
應(yīng)用操作實(shí)現(xiàn)代碼:
public void Tags4()
{
using (var context = new TagsDbContext())
{
//1.添加post-tag
var postAdd = new Post4 { UserId = 1, Title = "title", Content = "content" };
context.Post4s.Add(postAdd);
var tagAdd1 = context.Tag4s.FirstOrDefault(t => t.TagName == ".net" && t.UserId == 1);
var tagAdd2 = context.Tag4s.FirstOrDefault(t => t.TagName == "asp.net vnext" && t.UserId == 1);
if (tagAdd1 != null)
tagAdd1.UseCount++;
else
context.Tag4s.Add(new Tag4 { UserId = 1, TagName = ".net", UseCount = 1 });
if (tagAdd2 != null)
tagAdd1.UseCount++;
else
context.Tag4s.Add(new Tag4 { UserId = 1, TagName = "asp.net vnext", UseCount = 1 });
context.TagMap4s.Add(new TagMap4 { PostId = postAdd.PostId, TagId = tagAdd1.TagId });
context.TagMap4s.Add(new TagMap4 { PostId = postAdd.PostId, TagId = tagAdd2.TagId });
//2.單獨(dú)修改tag
var tagModify = context.Tag4s.Where(t => t.TagName == "asp.net vnext" && t.UserId == 1).FirstOrDefault();
tagModify.TagName = "asp.net5";
//3.在post中修改tag
var tagModify2 = (from t in context.Tag4s
where t.UserId == 1 && t.TagName == "asp.net vnext"
join m in context.TagMap4s on t.TagId equals m.TagId
where m.PostId == 1
select t).FirstOrDefault();
tagModify2.UseCount--;
var tagModify3 = context.Tag4s.FirstOrDefault(t => t.TagName == "asp.net 5" && t.UserId == 1);
if (tagModify3 != null)
tagModify3.UseCount++;
else
context.Tag4s.Add(new Tag4 { UserId = 1, TagName = "asp.net 5", UseCount = 1 });
var postModify = context.Post4s.FirstOrDefault(p => p.PostId == 1 && p.UserId == 1);
var tagMapDelete= context.TagMap4s.FirstOrDefault(p => p.PostId == 1 && p.TagId == tagModify2.TagId);
context.TagMap4s.Remove(tagMapDelete);
postModify.TagMap4s.Add(new TagMap4 { PostId = postModify.PostId, TagId = tagModify3.TagId });
//4.單獨(dú)刪除tag
var tagDelete = context.Tag4s.Where(t => t.TagName == "asp.net vnext" && t.UserId == 1).FirstOrDefault();
var tagMapsDelete = context.TagMap4s.Where(t => t.TagId == tagDelete.TagId).ToList();
context.Tag4s.Remove(tagDelete);
context.TagMap4s.RemoveRange(tagMapsDelete);
//5.在post中刪除tag
var tagDelete2 = (from t in context.Tag4s
where t.UserId == 1 && t.TagName == "asp.net vnext"
join m in context.TagMap4s on t.TagId equals m.TagId
where m.PostId == 1
select t).FirstOrDefault();
tagDelete2.UseCount--;
var tagMapDelete2 = context.TagMap4s.FirstOrDefault(p => p.PostId == 1 && p.TagId == tagDelete2.TagId);
context.TagMap4s.Remove(tagMapDelete2);
//6.查詢tag(帶數(shù)量統(tǒng)計(jì))
var tagsSelect = context.Tag4s.Where(t => t.UserId == 1).ToList();
//7.查詢post(tag展示)
var postSelect = context.Post4s.FirstOrDefault(p =>p.PostId == 1 && p.UserId == 1);
var tagsSelect2 = (from t in context.Tag4s
where t.UserId == 1
join m in context.TagMap4s on t.TagId equals m.TagId
select t).ToList();
var tags = string.Join(",", tagsSelect2.Select(t => t.TagName));
//8.根據(jù)tag查詢post
var postTagSelect = from p in context.Post4s
join m in context.TagMap4s on p.PostId equals m.PostId
join t in context.Tag4s on m.TagId equals t.TagId
where t.TagName == "asp.net5" && p.UserId == 1 && t.UserId == 1
select p;
context.SaveChanges();
}
}
結(jié)論:單從代碼量上來說,這種應(yīng)用操作實(shí)現(xiàn)代碼量最多,其實(shí)大部分操作都是在判斷 Tag,也就是為了利用現(xiàn)有的 Tag 數(shù)據(jù),并不是像前面兩種,不管 Tag 是否存在,直接添加、修改和刪除,統(tǒng)計(jì) Tag 使用數(shù)量實(shí)現(xiàn),是四種方案中最簡單的,其余的應(yīng)用操作,因?yàn)槟P蛯蛹?jí)越多、關(guān)聯(lián)越多,操作起來就會(huì)越復(fù)雜,但不可否認(rèn),這種設(shè)計(jì),是四種方案中“最理想”的。
5. 簡要總結(jié)
深入去設(shè)計(jì)并實(shí)現(xiàn)這四種 Tag 模型方案,其實(shí)有很多的感觸,是之前沒實(shí)現(xiàn)體會(huì)不到的,比如:
模型的簡單和復(fù)雜是相對(duì)的:并不是模型越簡單越好,也不是越復(fù)雜越好,第一和第二種方案就說明這點(diǎn)。
模型的設(shè)計(jì)是相對(duì)于應(yīng)用場景的:在不能確定應(yīng)用場景的情況下,不能說哪種模型設(shè)計(jì)是好是壞,交通工具有很多種,飛機(jī)快過汽車,但飛機(jī)在陸地上跑不過汽車。
最后,簡要總結(jié)下四種 Tag 模型設(shè)計(jì)的一些應(yīng)用場景:
1. Tag 存在于 Post 中:1.3.5.7.8 應(yīng)用操作,不要求獨(dú)立對(duì) Tag 進(jìn)行操作和數(shù)量統(tǒng)計(jì)。
2. Tag 獨(dú)立 Post(一對(duì)多關(guān)系):1-8 應(yīng)用操作,數(shù)據(jù)量不是很大,對(duì) Tag 操作比較頻繁,對(duì) Post 操作不頻繁。
3. Tag 獨(dú)立 Post(一對(duì)多關(guān)系),Post 中多一個(gè) Tags:1-8 應(yīng)用操作,數(shù)據(jù)量不是很大,對(duì) Post 操作比較頻繁,對(duì) Tag 操作不頻繁。
4. Tag 和 Post 都獨(dú)立,創(chuàng)建 TagMap 映射(多對(duì)多關(guān)系):1-8 應(yīng)用操作,Tag 業(yè)務(wù)變化比較頻繁,對(duì) Tag 和 Post 操作都比較頻繁。
對(duì)于我自己來說,上面四種 Tag 模型設(shè)計(jì),我最偏向于第二種和第四種,如果非要選擇一種的話,我可能會(huì)選擇第二種,為什么呢?因?yàn)檎l寫過應(yīng)用操作的實(shí)現(xiàn)代碼,誰知道,哈哈!!!
相關(guān)參考資料:
如何設(shè)計(jì)一款高效的TAG索引系統(tǒng)
如何進(jìn)行文章分類和標(biāo)簽的數(shù)據(jù)庫設(shè)計(jì)
探討下Tag標(biāo)簽的數(shù)據(jù)庫設(shè)計(jì)(千萬級(jí)數(shù)據(jù)量)
開發(fā)高效的Tag標(biāo)簽系統(tǒng)數(shù)據(jù)庫設(shè)計(jì)
「用戶標(biāo)簽」在數(shù)據(jù)庫設(shè)計(jì)時(shí)應(yīng)該如何存儲(chǔ)?
標(biāo)簽 (Tags) 是個(gè)好的設(shè)計(jì)嗎?
標(biāo)簽(Tag)的數(shù)據(jù)庫設(shè)計(jì)
關(guān)于數(shù)據(jù)庫中Tag的設(shè)計(jì)
未完待續(xù)。。。
作者:田園里的蟋蟀
出處:http://www.cnblogs.com/xishuai/
總結(jié)
以上是生活随笔為你收集整理的标签(Tag)的各种设计方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 婚外恋电影(十部告诉你什么是真爱的婚外情
- 下一篇: 慢性荨麻疹怎么治根(慢性荨麻疹治疗建议)