ServiceStack.Redis 中关系操作的局限与bug
redis是文档型的,nosql中难处理的是关系。
比如人可以发博客,博客可以有分类。按照传统sql中,用户表和分类表都是主表,博客表是从表,有用户的外键和分类的外键
如果使用文档型的思考方式。
为用户A(User id=1)存储他的博客,在redis中是list或set
为分类A(Cate id=1)存储分类下的博客,在redis中是list或set
则当用户A向分类A中添加一条新博客时,需要同时向两个list(或set)中增加数据,而且理论上应该是事务的,修改的时候也需要同时修改两个。
这样的好处是读操作是完全优化的,直接从一个key中读出来的东西,马上就可以用
坏处是写操作太复杂,稍不注意可能就漏掉什么东西,更新博客需要更新非常多个list中的元素。
ServiceStack的redis客户端专门为这种情况提供了几个方法。
先来看实体类
public class User { public int Id { get; set; } public string Name { get; set; } } public class Blog { public int Id { get; set; } public string Title { get; set; } } public class Cate { public int Id { get; set; } public string Name { get; set; } }
很简单的3个类,用于表示用户,分类,博客3种概念
使用强类型的client保存3个实例
var clientsManager = new PooledRedisClientManager(); using (IRedisClient redis = clientsManager.GetClient()) { redis.FlushAll(); var u = new User { Id = 1, Name = "A" }; var c = new Cate { Id = 1, Name = "A" }; var blog = new Blog { Id = 1, Title = "blog" }; redis.As<User>().Store(u); redis.As<Cate>().Store(c); redis.As<Blog>().Store(blog); }

可以通过客户端软件查看,3个实体都保存成功,但是并没有体现关系
redis.As<User>().StoreRelatedEntities(u.Id, blog); redis.As<Cate>().StoreRelatedEntities(c.Id, blog);
之后调用保存关系的语句,as的是主表,第一个是主表主键,第二个是从对象
redis中,新建了2个key,ref:Cate/Blog:1和ref:User/Blog:1
他们的值是一个set
set中的具体内容并不是对象本身,而是对象在urn中的key
var blogs = redis.As<User>().GetRelatedEntities<Blog>(u.Id);
可以通过相关语句来获取从表内容
直接取到了blog的实体
但是在删除的时候有一个bug
他的方法指定的第二个参数是childId,所以我们传进去id,但是删除不掉
redis.As<User>().DeleteRelatedEntity<Blog>(u.Id, blog);
不使用id,而使用对象,也依然删除不掉
查看源码发现,当他运行从set中删除东西的时候,找key是对的,但是要被删掉的元素生成的不对
添加的时候,他拿UrnKey<T>(x)生成了实体保存的key,而删除的时候没有
删除的时候,直接是序列化的,则1,序列化后就是1,而我们的set中,并没有1这个值,所以是没有删掉任何东西的。
client的UrnKey是个internal的方法,再次被恶心了
redis.As<User>().DeleteRelatedEntity<Blog>(u.Id, (redis as RedisNativeClient).NamespacePrefix + IdUtils.CreateUrn(blog));
我们只能使用这么复杂的方式,等于把他内部的代码都拿到外面来处理了,当然你可以clone他的源码去改或者写扩展方法。
根据关系的key,我们大概可以分析出
ref:主表/从表:主表主键值
但这样的方式有一定的局限性,就是对同一个主从类型,他们之间只能表达一种关系。
比如人与博客,如果我需要表达 人写的博客,人推荐的博客 这两种关系(都是人与博客的),则无法实现
比如User 1,他写了Blog 1,推荐了Blog 2。但是他们都会被加入到ref:User/Blog:1中,无法区分是他写的还是他推荐的。
所以我们需要为两种类型之间的关系去给一个名字,来区分到底是那种关系
在RedisTypedClient<T>中有一个GetChildReferenceSetKey方法,是来生成这个key的,private方法,再次被恶心
当然,可以通过对NamespacePrefix设置一个不同的值来区分,但是感觉上怪怪的,因为这个在我看来是不同的应用程序,为防止key重复而设置的
有兴趣的朋友可以写几个扩展方法,反正源码基本都能看到
再再再次被恶心到的是,竟然github没有开放issues提交
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?