RedisOM for .NET
了解如何使用 Redis Stack 和 .NET 进行构建
Redis OM .NET 是一个专门用于处理 Redis Stack 中文档的库。在本教程中,我们将构建一个简单的 ASP.NET Core Web-API 应用程序,用于对一个简单的 Person 和 Address 模型执行 CRUD 操作,我们将使用 Redis OM .NET 完成所有这些任务。
前提条件
- .NET 6 SDK
- 用于编写 .NET 的任何 IDE(Visual Studio、Rider、Visual Studio Code)。
- RediSearch 必须作为 Redis Stack 配置的一部分进行安装。
- 可选:Docker Desktop 用于在 Docker 中运行 redis-stack 以进行本地测试。
跳至代码
如果你想跳过本教程直接查看代码,所有源代码都可在 GitHub 上找到
运行 Redis Stack
运行 Redis Stack 的方法有很多种。一种方法是使用 Docker 镜像
docker run -d -p 6379:6379 -p 8001:8001 redis/redis-stack
创建项目
要创建项目,只需运行
dotnet new webapi -n Redis.OM.Skeleton --no-https --kestrelHttpPort 5000
然后在你选择的 IDE 中打开 Redis.OM.Skeleton.csproj
文件。
配置应用程序
在你的 appsettings.json
文件中添加一个 REDIS_CONNECTION_STRING
字段来配置应用程序。将连接字符串设置为你的 Redis 实例的 URI。如果使用前面提到的 Docker 命令,你的连接字符串将是 redis://localhost:6379
。
连接字符串规范
Redis URI 的规范位于此处。对于不包含 username
的连接字符串,你可以使用 :password@host:port
或 default:password@host:port
。
创建模型
确保将 Redis.OM
包添加到你的项目中。这个包可以轻松创建模型并查询你的 Redis 领域对象。
dotnet add package Redis.OM
现在是时候创建应用程序将用于存储/检索人员的 Person
/Address
模型了。创建一个名为 Model
的新目录,并将 Address.cs
和 Person.cs
文件添加到其中。在 Address.cs
中,添加以下内容
using Redis.OM.Modeling;
namespace Redis.OM.Skeleton.Model;
public class Address
{
[Indexed]
public int? StreetNumber { get; set; }
[Indexed]
public string? Unit { get; set; }
[Searchable]
public string? StreetName { get; set; }
[Indexed]
public string? City { get; set; }
[Indexed]
public string? State { get; set; }
[Indexed]
public string? PostalCode { get; set; }
[Indexed]
public string? Country { get; set; }
[Indexed]
public GeoLoc Location { get; set; }
}
在这里,你会注意到除了标记为 Searchable
的 StreetName
之外,所有字段都使用 Indexed
属性进行修饰。这些属性(Searchable
和 Indexed
)告诉 Redis OM 你希望在查询 Redis Stack 中的文档时能够在查询中使用这些字段。Address
本身不会是一个文档,因此顶级类没有用任何东西修饰;相反,Address
模型将嵌入到我们的 Person
模型中。
为此,请将以下内容添加到 Person.cs
中
using Redis.OM.Modeling;
namespace Redis.OM.Skeleton.Model;
[Document(StorageType = StorageType.Json, Prefixes = new []{"Person"})]
public class Person
{
[RedisIdField] [Indexed]public string? Id { get; set; }
[Indexed] public string? FirstName { get; set; }
[Indexed] public string? LastName { get; set; }
[Indexed] public int Age { get; set; }
[Searchable] public string? PersonalStatement { get; set; }
[Indexed] public string[] Skills { get; set; } = Array.Empty<string>();
[Indexed(CascadeDepth = 1)] public Address? Address { get; set; }
}
这里有几点需要注意
-
[Document(StorageType = StorageType.Json, Prefixes = new []{"Person"})]
指示 Redis OM 将用于在 Redis 中存储文档的数据类型是 JSON,并且Person
类的键前缀将是Person
。 -
[Indexed(CascadeDepth = 1)] Address? Address { get; set; }
是使用 Redis OM 索引嵌入对象两种方法之一。这种方法指示索引级联到对象图中的对象,CascadeDepth
为 1 意味着它将仅遍历一层,就像从头构建索引一样索引对象。另一种方法是使用你想要搜索的各个索引字段的JsonPath
属性。这种更精确的方法限制了索引的大小。 -
Id
属性被标记为RedisIdField
。这表示该字段将用于在 Redis 中存储文档时生成文档的键名。
创建索引
构建模型后,下一步是在 Redis 中创建索引。管理此过程最正确的方法是将索引创建分离到一个 Hosted Service 中,该服务将在应用程序启动时运行。创建一个名为 HostedServices
的目录,并将 IndexCreationService.cs
添加到其中。在该文件中,添加以下内容,这将在启动时创建索引。
using Redis.OM.Skeleton.Model;
namespace Redis.OM.Skeleton.HostedServices;
public class IndexCreationService : IHostedService
{
private readonly RedisConnectionProvider _provider;
public IndexCreationService(RedisConnectionProvider provider)
{
_provider = provider;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
await _provider.Connection.CreateIndexAsync(typeof(Person));
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
接下来,将以下内容添加到 Program.cs
中以在启动时注册服务
builder.Services.AddHostedService<IndexCreationService>();
注入 RedisConnectionProvider
Redis OM 使用 RedisConnectionProvider
类处理与 Redis 的连接,并提供可用于与 Redis 交互的类。要使用它,只需将 RedisConnectionProvider
实例注入到你的应用程序中。在你的 Program.cs
文件中,添加
builder.Services.AddSingleton(new RedisConnectionProvider(builder.Configuration["REDIS_CONNECTION_STRING"]));
这将从配置中提取连接字符串并初始化提供程序。现在,该提供程序将在你的控制器/服务中可用。
创建 PeopleController
最后一块拼图是为我们的 People API 编写实际的 API 控制器。在 controllers
目录中,添加 PeopleController.cs
文件,PeopleController
类的骨架将是
using Microsoft.AspNetCore.Mvc;
using Redis.OM.Searching;
using Redis.OM.Skeleton.Model;
namespace Redis.OM.Skeleton.Controllers;
[ApiController]
[Route("[controller]")]
public class PeopleController : ControllerBase
{
}
注入 RedisConnectionProvider
为了与 Redis 交互,注入 RedisConnectionProvider。在此依赖注入过程中,提取一个 RedisCollection<Person>
实例,它将提供一个流畅的接口用于查询 Redis 中的文档。
private readonly RedisCollection<Person> _people;
private readonly RedisConnectionProvider _provider;
public PeopleController(RedisConnectionProvider provider)
{
_provider = provider;
_people = (RedisCollection<Person>)provider.RedisCollection<Person>();
}
添加创建 Person 的路由
要添加到 API 的第一个路由是用于创建 Person 的 POST 请求,使用 RedisCollection
,只需调用 InsertAsync
并传入 Person 对象即可。
[HttpPost]
public async Task<Person> AddPerson([FromBody] Person person)
{
await _people.InsertAsync(person);
return person;
}
添加按年龄过滤的路由
要添加到 API 的第一个过滤路由将允许用户按最小和最大年龄进行过滤。使用 RedisCollection
可用的 LINQ 接口,这是一个简单的操作
[HttpGet("filterAge")]
public IList<Person> FilterByAge([FromQuery] int minAge, [FromQuery] int maxAge)
{
return _people.Where(x => x.Age >= minAge && x.Age <= maxAge).ToList();
}
按地理位置过滤
Redis OM 有一个 GeoLoc
数据结构,它的一个实例由 Address
模型索引,使用 RedisCollection
,可以通过 GeoFilter
方法和你想过滤的字段来查找特定位置半径内的所有对象。
[HttpGet("filterGeo")]
public IList<Person> FilterByGeo([FromQuery] double lon, [FromQuery] double lat, [FromQuery] double radius, [FromQuery] string unit)
{
return _people.GeoFilter(x => x.Address!.Location, lon, lat, radius, Enum.Parse<GeoLocDistanceUnit>(unit)).ToList();
}
按精确字符串过滤
当模型中的字符串属性标记为 Indexed
时,例如 FirstName
和 LastName
,Redis OM 可以对其执行精确文本匹配。例如,以下两个路由按 PostalCode
和名称进行过滤,演示了精确字符串匹配。
[HttpGet("filterName")]
public IList<Person> FilterByName([FromQuery] string firstName, [FromQuery] string lastName)
{
return _people.Where(x => x.FirstName == firstName && x.LastName == lastName).ToList();
}
[HttpGet("postalCode")]
public IList<Person> FilterByPostalCode([FromQuery] string postalCode)
{
return _people.Where(x => x.Address!.PostalCode == postalCode).ToList();
}
使用全文搜索进行过滤
当模型中的属性标记为 Searchable
时,如 StreetAddress
和 PersonalStatement
,你可以执行全文搜索,请参见 PersonalStatement
和 StreetAddress
的过滤器。
[HttpGet("fullText")]
public IList<Person> FilterByPersonalStatement([FromQuery] string text){
return _people.Where(x => x.PersonalStatement == text).ToList();
}
[HttpGet("streetName")]
public IList<Person> FilterByStreetName([FromQuery] string streetName)
{
return _people.Where(x => x.Address!.StreetName == streetName).ToList();
}
按数组成员过滤
当字符串数组或列表标记为 Indexed
时,Redis OM 可以使用数组或列表的 Contains
方法过滤所有包含给定字符串的记录。例如,我们的 Person
模型有一个技能列表,你可以通过添加以下路由进行查询。
[HttpGet("skill")]
public IList<Person> FilterBySkill([FromQuery] string skill)
{
return _people.Where(x => x.Skills.Contains(skill)).ToList();
}
更新 Person
使用 Redis OM 更新 Redis Stack 中的文档可以通过先实例化 Person 对象,进行所需的更改,然后调用集合上的 Save
方法来完成。集合负责跟踪其中实例化的实体所做的更新;因此,它将跟踪并应用你在其中进行的任何更新。例如,添加以下路由以根据给定 ID 更新 Person 的年龄。
[HttpPatch("updateAge/{id}")]
public IActionResult UpdateAge([FromRoute] string id, [FromBody] int newAge)
{
foreach (var person in _people.Where(x => x.Id == id))
{
person.Age = newAge;
}
_people.Save();
return Accepted();
}
删除 Person
可以使用 Unlink
从 Redis 中删除文档。只需调用 Unlink
并传入键名即可。给定 ID,我们可以使用前缀和 ID 重构键名。
[HttpDelete("{id}")]
public IActionResult DeletePerson([FromRoute] string id)
{
_provider.Connection.Unlink($"Person:{id}");
return NoContent();
}
运行应用程序
现在剩下的就是运行应用程序并进行测试了。你可以通过运行 dotnet run
来实现,应用程序现在暴露在 5000 端口,并且应该有一个 Swagger UI,你可以在 http://localhost:5000/swagger 使用它来测试 API。在 GitHub 仓库中,有一些脚本和数据文件,可以使用 API 将一些人员插入到 Redis 中。
使用 Redis Insight 查看数据
你可以安装 Redis Insight GUI,或者使用运行在 http://localhost:8001/ 上的 Redis Insight GUI。
你可以按照以下步骤查看数据
- 接受 EULA
- 点击添加 Redis 数据库按钮
- 输入你的 Redis 服务器的主机名和端口名。如果你正在使用 Docker 镜像,它们分别是
localhost
和6379
,并为你的数据库指定一个别名。
- 点击
添加 Redis 数据库
。
资源
本教程的源代码可在 GitHub 上找到。