.NET中使用Redis (二)
很久以前写了一篇文章 .NET中使用Redis 介绍了如何安装Redis服务端,以及如何在.NET中调用Redis读取数据。本文简单介绍如何设计NoSQL数据库,以及如何使用Redis来存储对象。
和传统的关系型数据库不同,NoSQL大部分都是以键值对存储在内存中的,我们不能直接把RDBMS里面的一些做法直接移植到NoSQL中来,一个最主要的原因是,在NoSQL中缺少RDBMS中的一些诸如join ,union以及一些在关系型数据库中效率很高的执行语句,这些在NoSQL不能很好的支持,或者说效率低。
下文首先通过例子介绍在SQLServer中设计一个DB系统以及与NoSQL环境中设计一个DB的区别,最后演示如何在Redis中对数据进行读写操作。
一个简单的博客系统
假设我们要设计一个简单的博客系统,用户可以注册一个博客(Blog),然后可以在上面写文章(Post),文章可以分类(Category)以及添加标签(Tag),用户可以对文章进行评论(Comment)。
在该系统中,我们需要实现,如下基本功能:
- 首页:显示所有人的博客
- 首页:显示最近的所有发表的文章
- 首页:显示所有的最近的评论
- 首页:显示博客的标签云
- 首页:显示所有的分类
- 文章页面:显示文章以及所有的评论
- 文章页面:添加评论
- 标签页面:显示所有标签以及标签对应的文章
- 分类页面:显示所有分类以及分类对应的文章
如果在SQLServer中,相信很简单就可以设计出这样一个DB了。

在NoSQL环境中,我们不能直接将上面的结构搬进来,所以需要根据需求重新设计我们的模型。
定义实体
在NoSQL环境下,所有的数据其实都是以key和value的形式存储在内存中的,value通常是序列化为字符串保存的。我们使用redis客户端的时候,可以直接将对象存储,这些客户端在内部实现上帮助我们进行了序列化。所以第一步就是需要定义实体模型:
首先来看User实体:
public class User
{
public User()
{
this.BlogIds = new List<long>();
}
public long Id { get; set; }
public string Name { get; set; }
public List<long> BlogIds { get; set; }
}
User实体中,包含了用户的Id,Name以及博客的Id。
然后Blog实体:
public class Blog
{
public Blog()
{
this.Tags = new List<string>();
this.BlogPostIds = new List<long>();
}
public long Id { get; set; }
public long UserId { get; set; }
public string UserName { get; set; }
public List<string> Tags { get; set; }
public List<long> BlogPostIds { get; set; }
}
包含了标签Tag,以及文章Id列表。
文章BolgPost实体:
public class BlogPost
{
public BlogPost()
{
this.Categories = new List<string>();
this.Tags = new List<string>();
this.Comments = new List<BlogPostComment>();
}
public long Id { get; set; }
public long BlogId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public List<string> Categories { get; set; }
public List<string> Tags { get; set; }
public List<BlogPostComment> Comments { get; set; }
}
包含了一篇文章的基本信息,如文章分类,文章标签,文章的评论。
最后看评论BlogPostComment实体:
public class BlogPostComment
{
public string Content { get; set; }
public DateTime CreatedDate { get; set; }
}

具体实现
实体定义好了之后,我们就可以开始具体实现了。为了演示,这里通过单元测试的方式实现具体功能:
首先要把Redis的服务端启动起来,然后在工程中新建一个Redis客户端,之后的所有操作都通过这个客户端进行。
[TestFixture, Explicit, Category("Integration")]
public class BlogPostExample
{
readonly RedisClient redis = new RedisClient("localhost");
[SetUp]
public void OnBeforeEachTest()
{
redis.FlushAll();
InsertTestData();
}
}
在单元测试的SetUp中,我们插入一些模拟数据,插入数据的方法为InsetTestData方法:
public void InsertTestData()
{
var redisUsers = redis.As<User>();
var redisBlogs = redis.As<Blog>();
var redisBlogPosts = redis.As<BlogPost>();
var yangUser = new User { Id = redisUsers.GetNextSequence(), Name = "Eric Yang" };
var zhangUser = new User { Id = redisUsers.GetNextSequence(), Name = "Fish Zhang" };
var yangBlog = new Blog
{
Id = redisBlogs.GetNextSequence(),
UserId = yangUser.Id,
UserName = yangUser.Name,
Tags = new List<string> { "Architecture", ".NET", "Databases" },
};
var zhangBlog = new Blog
{
Id = redisBlogs.GetNextSequence(),
UserId = zhangUser.Id,
UserName = zhangUser.Name,
Tags = new List<string> { "Architecture", ".NET", "Databases" },
};
var blogPosts = new List<BlogPost>
{
new BlogPost
{
Id = redisBlogPosts.GetNextSequence(),
BlogId = yangBlog.Id,
Title = "Memcache",
Categories = new List<string> { "NoSQL", "DocumentDB" },
Tags = new List<string> {"Memcache", "NoSQL", "JSON", ".NET"} ,
Comments = new List<BlogPostComment>
{
new BlogPostComment { Content = "First Comment!", CreatedDate = DateTime.UtcNow,},
new BlogPostComment { Content = "Second Comment!", CreatedDate = DateTime.UtcNow,},
}
},
new BlogPost
{
Id = redisBlogPosts.GetNextSequence(),
BlogId = zhangBlog.Id,
Title = "Redis",
Categories = new List<string> { "NoSQL", "Cache" },
Tags = new List<string> {"Redis", "NoSQL", "Scalability", "Performance"},
Comments = new List<BlogPostComment>
{
new BlogPostComment { Content = "First Comment!", CreatedDate = DateTime.UtcNow,}
}
},
new BlogPost
{
Id = redisBlogPosts.GetNextSequence(),
BlogId = yangBlog.Id,
Title = "Cassandra",
Categories = new List<string> { "NoSQL", "Cluster" },
Tags = new List<string> {"Cassandra", "NoSQL", "Scalability", "Hashing"},
Comments = new List<BlogPostComment>
{
new BlogPostComment { Content = "First Comment!", CreatedDate = DateTime.UtcNow,}
}
},
new BlogPost
{
Id = redisBlogPosts.GetNextSequence(),
BlogId = zhangBlog.Id,
Title = "Couch Db",
Categories = new List<string> { "NoSQL", "DocumentDB" },
Tags = new List<string> {"CouchDb", "NoSQL", "JSON"},
Comments = new List<BlogPostComment>
{
new BlogPostComment {Content = "First Comment!", CreatedDate = DateTime.UtcNow,}
}
},
};
yangUser.BlogIds.Add(yangBlog.Id);
yangBlog.BlogPostIds.AddRange(blogPosts.Where(x => x.BlogId == yangBlog.Id).Map(x => x.Id));
zhangUser.BlogIds.Add(zhangBlog.Id);
zhangBlog.BlogPostIds.AddRange(blogPosts.Where(x => x.BlogId == zhangBlog.Id).Map(x => x.Id));
redisUsers.Store(yangUser);
redisUsers.Store(zhangUser);
redisBlogs.StoreAll(new[] { yangBlog, zhangBlog });
redisBlogPosts.StoreAll(blogPosts);
}
在方法中,首先在Redis中创建了三个强类型的IRedisTypedClient类型的对象redisUsers,redisBlogs,redisBlogPosts来保存用户信息,博客信息,和文字信息。
var yangUser = new User { Id = redisUsers.GetNextSequence(), Name = "Eric Yang" };
在新建用户的时候,因为Id是自增字段,所以直接调用redisUsers这个client的GetNextSequence()方法就可以获得一个自增的Id。
创建完用户之后,接着创建博客信息:
var yangBlog = new Blog
{
Id = redisBlogs.GetNextSequence(),
UserId = yangUser.Id,
UserName = yangUser.Name,
Tags = new List<string> { "Architecture", ".NET", "Databases" },
};
该博客有几个标签。
在接着创建该博客上发表的若干篇文章:
var blogPosts = new List<BlogPost>
{
new BlogPost
{
Id = redisBlogPosts.GetNextSequence(),
BlogId = yangBlog.Id,
Title = "Memcache",
Categories = new List<string> { "NoSQL", "DocumentDB" },
Tags = new List<string> {"Memcache", "NoSQL", "JSON", ".NET"} ,
Comments = new List<BlogPostComment>
{
new BlogPostComment { Content = "First Comment!", CreatedDate = DateTime.UtcNow,},
new BlogPostComment { Content = "Second Comment!", CreatedDate = DateTime.UtcNow,},
}
}
}
每一篇文章都有分类和标签,以及评论。
然后需要给user的BlogsIds和blog的BlogPostIds赋值
yangUser.BlogIds.Add(yangBlog.Id); yangBlog.BlogPostIds.AddRange(blogPosts.Where(x => x.BlogId == yangBlog.Id).Map(x => x.Id));
最后需要把这些信息保存到redis中。
//保存用户信息
redisUsers.Store(yangUser);
redisUsers.Store(zhangUser);
//保存博客信息
redisBlogs.StoreAll(new[] { yangBlog, zhangBlog });
//保存所有的文章信息
redisBlogPosts.StoreAll(blogPosts);
现在,利用Redis Desktop Manager,可以查看Reidis中存储的数据:

数据准备好了之后,可以实现前面列出的一系列方法了:
显示所有博客
该方法在GetAllBlogs中,实现如下:
[Test]
public void Show_a_list_of_blogs()
{
var redisBlogs = redis.As<Blog>();
var blogs = redisBlogs.GetAll();
blogs.PrintDump();
}
只需要调用GetAll方法即可获取内存中的所有指定类型的对象。
输出结果为:
[
{
Id: 1,
UserId: 1,
UserName: Eric Yang,
Tags:
[
Architecture,
.NET,
Databases
],
BlogPostIds:
[
1,
3
]
},
{
Id: 2,
UserId: 2,
UserName: Fish Zhang,
Tags:
[
Architecture,
.NET,
Databases
],
BlogPostIds:
[
2,
4
]
}
]
显示最近发表的文章和评论
实现如下:
[Test]
public void Show_a_list_of_recent_posts_and_comments()
{
//Get strongly-typed clients
var redisBlogPosts = redis.As<BlogPost>();
var redisComments = redis.As<BlogPostComment>();
{
//To keep this example let's pretend this is a new list of blog posts
var newIncomingBlogPosts = redisBlogPosts.GetAll();
//Let's get back an IList<BlogPost> wrapper around a Redis server-side List.
var recentPosts = redisBlogPosts.Lists["urn:BlogPost:RecentPosts"];
var recentComments = redisComments.Lists["urn:BlogPostComment:RecentComments"];
foreach (var newBlogPost in newIncomingBlogPosts)
{
//Prepend the new blog posts to the start of the 'RecentPosts' list
recentPosts.Prepend(newBlogPost);
//Prepend all the new blog post comments to the start of the 'RecentComments' list
newBlogPost.Comments.ForEach(recentComments.Prepend);
}
//Make this a Rolling list by only keep the latest 3 posts and comments
recentPosts.Trim(0, 2);
recentComments.Trim(0, 2);
//Print out the last 3 posts:
recentPosts.GetAll().PrintDump();
recentComments.GetAll().PrintDump();
}
}
方法中定义了两个key为urn:BlogPost:RecentPosts 和 urn:BlogPostComment:RecentComments的 List对象来保存最近发表的文章和评论:recentPosts.Prepend(newBlogPost)方法表示将新创建的文章插到recentPosts列表的最前面。
Trim方法表示仅保留n个在集合中。
显示博客的标签云
显示博客的标签云方法如下:
[Test]
public void Show_a_TagCloud()
{
//Get strongly-typed clients
var redisBlogPosts = redis.As<BlogPost>();
var newIncomingBlogPosts = redisBlogPosts.GetAll();
foreach (var newBlogPost in newIncomingBlogPosts)
{
//For every tag in each new blog post, increment the number of times each Tag has occurred
newBlogPost.Tags.ForEach(x =>
redis.IncrementItemInSortedSet("urn:TagCloud", x, 1));
}
//Show top 5 most popular tags with their scores
var tagCloud = redis.GetRangeWithScoresFromSortedSetDesc("urn:TagCloud", 0, 4);
tagCloud.PrintDump();
}
显示标签云的实现,用到了redis中的SortedSet,IncrementItemInSortedSet表示如果有相同的话,值加一,GetRangeWithScoresFromSortedSetDesc方法,获取某一key的前5个对象。
显示所有的分类
显示所有的分类用到了Set对象。
[Test]
public void Show_all_Categories()
{
var redisBlogPosts = redis.As<BlogPost>();
var blogPosts = redisBlogPosts.GetAll();
foreach (var blogPost in blogPosts)
{
blogPost.Categories.ForEach(x =>
redis.AddItemToSet("urn:Categories", x));
}
var uniqueCategories = redis.GetAllItemsFromSet("urn:Categories");
uniqueCategories.PrintDump();
}
显示文章以及其评论
实现如下:
[Test]
public void Show_post_and_all_comments()
{
//There is nothing special required here as since comments are Key Value Objects
//they are stored and retrieved with the post
var postId = 1;
var redisBlogPosts = redis.As<BlogPost>();
var selectedBlogPost = redisBlogPosts.GetById(postId.ToString());
selectedBlogPost.PrintDump();
}
只需要把postId传进去就可以通过GetById的方法获取内存中的对象.
添加评论
首先根据PostId获取BlogPost,然后在Comment属性中添加一个BlogPostComment对象,然后在保存改BlogPost.
[Test]
public void Add_comment_to_existing_post()
{
var postId = 1;
var redisBlogPosts = redis.As<BlogPost>();
var blogPost = redisBlogPosts.GetById(postId.ToString());
blogPost.Comments.Add(
new BlogPostComment { Content = "Third Post!", CreatedDate = DateTime.UtcNow });
redisBlogPosts.Store(blogPost);
var refreshBlogPost = redisBlogPosts.GetById(postId.ToString());
refreshBlogPost.PrintDump();
}
显示分类以及分类对应的文章
[Test]
public void Show_all_Posts_for_the_DocumentDB_Category()
{
var redisBlogPosts = redis.As<BlogPost>();
var newIncomingBlogPosts = redisBlogPosts.GetAll();
foreach (var newBlogPost in newIncomingBlogPosts)
{
//For each post add it's Id into each of it's 'Cateogry > Posts' index
newBlogPost.Categories.ForEach(x =>
redis.AddItemToSet("urn:Category:" + x, newBlogPost.Id.ToString()));
}
//Retrieve all the post ids for the category you want to view
var documentDbPostIds = redis.GetAllItemsFromSet("urn:Category:DocumentDB");
//Make a batch call to retrieve all the posts containing the matching ids
//(i.e. the DocumentDB Category posts)
var documentDbPosts = redisBlogPosts.GetByIds(documentDbPostIds);
documentDbPosts.PrintDump();
}
这里首先把所有的文章按照标签新建Set,把相同的分类的文章放到一个Set中,最后根据key即可查找到相应的集合。
总结
本文利用一个简单的博客系统,简要介绍了如何利用Redis存储和获取复杂的数据。由于本文主要为了演示如何与Redis进行交互,所以实体设计的很简陋,没有按照DDD的思想进行设计,在某些设计方面没有遵循前文浅谈依赖注入中使用的原理和方法,后面会写文章对该系统进行重构以使之更加完善。
希望本文对您了解如何利用Redis存储复杂对象有所帮助。
参考资料
- Designing NoSql Database
- Migrations Using Schemaless NoSql
- That No SQL Thing: The relational modeling anti pattern in document databases
.NET中使用Redis (二)的更多相关文章
- 转:.NET中使用Redis (二)
原文来自于:http://blog.jobbole.com/83824/ 原文出处: 寒江独钓 欢迎分享原创到伯乐头条 很久以前写了一篇文章 .NET中使用Redis 介绍了如何安装Redis服务 ...
- 分布式数据存储 之 Redis(二) —— spring中的缓存抽象
分布式数据存储 之 Redis(二) -- spring中的缓存抽象 一.spring boot 中的 StringRedisTemplate 1.StringRedisTemplate Demo 第 ...
- (二)如何在.net中使用Redis
Step1:使用NuGet工具安装Redis C# API,这里有多个API我们可以使用其中一个:
- .NET中使用Redis(二)
很久以前写了一篇文章 .NET中使用Redis 介绍了如何安装Redis服务端,以及如何在.NET中调用Redis读取数据.本文简单介绍如何设计NoSQL数据库,以及如何使用Redis来存储对象. 和 ...
- Redis学习笔记之二 :在Java项目中使用Redis
成功配置redis之后,便来学习使用redis.首先了解下redis的数据类型. Redis的数据类型 Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set( ...
- C#中使用Redis学习二 在.NET4.5中使用redis hash操作
上一篇>> 摘要 上一篇讲述了安装redis客户端和服务器端,也大体地介绍了一下redis.本篇着重讲解.NET4.0 和 .NET4.5中如何使用redis和C# redis操作哈希表. ...
- 【转】C#中使用Redis学习二 在.NET4.5中使用redis hash操作
摘要 上一篇讲述了安装redis客户端和服务器端,也大体地介绍了一下redis.本篇着重讲解.NET4.0 和 .NET4.5中如何使用redis和C# redis操作哈希表.并且会将封装的一些代码贴 ...
- ABP中使用Redis Cache(1)
本文将讲解如何在ABP中使用Redis Cache以及使用过程中遇到的各种问题.下面就直接讲解使用步骤,Redis环境的搭建请直接网上搜索. 使用步骤: 一.ABP环境搭建 到http://www.a ...
- 在java中使用redis
在java中使用redis很简单,只需要添加jedist.jar,通过它的api就可以了.而且,api和redis的语法几乎完全相同.以下简单的测试: 参考:http://www.runoob.com ...
随机推荐
- 创建 OVS flat network - 每天5分钟玩转 OpenStack(134)
上一节完成了 flat 的配置工作,今天创建 OVS flat network.Admin -> Networks,点击 "Create Network" 按钮. 显示创建页 ...
- ASP.NET Aries 入门开发教程6:列表数据表格的格式化处理及行内编辑
前言: 为了赶进度,周末也写文了! 前几篇讲完查询框和工具栏,这节讲表格数据相关的操作. 先看一下列表: 接下来我们有很多事情可以做. 1:格式化 - 键值的翻译 对于“启用”列,已经配置了格式化 # ...
- ASP.NET Core 1.1 简介
ASP.NET Core 1.1 于2016年11月16日发布.这个版本包括许多伟大的新功能以及许多错误修复和一般的增强.这个版本包含了多个新的中间件组件.针对Windows的WebListener服 ...
- .NET Core系列 :3 、使用多个项目
通过前面的两篇文章,我们已经知道如何创建新的项目,如何生成并运行我们的应用程序,也知道(大致) project.json 文件中的内容是什么意思.但大多数项目往往也需要多个项目或引用的类库.我们要创建 ...
- 从中间件的历史来看移动App开发的未来
在移动开发领域我们发现一个很奇怪的现象:普通菜鸟新手经过3个月的培训就可以拿到 8K 甚至上万的工作:在北京稍微有点工作经验的 iOS 开发,就要求 2 万一个月的工资.不知道大家是否想过:移动应用开 ...
- mysql 7下载安装及问题解决
mysql 7安装及问题解决 一.mysql下载 下载地址:https://www.mysql.com/downloads/ Community (GPL) Downloads MySQL Commu ...
- 设计模式之行为类模式大PK
行为类模式大PK 行为类模式包括责任链模式.命令模式.解释器模式.迭代器模式.中介者模式.备忘录模式.观察者模式.状态模式.策略 ...
- CSS 3 学习——transform 3D转换渲染
以下内容根据官方规范翻译,没有翻译关于SVG变换的内容和关于矩阵计算的内容. 一般情况下,元素在一个无景深无立体感的平面(flat plane)上渲染,这个平面就是其包含块所处的平面.同时,页面上的其 ...
- 说说BPM数据表和日志表中几个状态字段的详细解释
有个客户说需要根据这些字段的值作为判断条件做一些定制化需求,所以需要知道这些字段的名词解释,以及里面存储的值具体代表什么意思 我只好为你们整理奉上这些了! Open Work Sheet 0 Sav ...
- 总结iOS开发中的断点续传那些事儿
前言 断点续传概述 断点续传就是从文件赏赐中断的地方重新开始下载或者上传数据,而不是从头文件开始.当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会从头下载,这样很 ...