一个通用的 .NET NoSQL 抽象层,使用类似 Entity-Framework 的存储库模式






4.93/5 (13投票s)
本文向您介绍一个开源解决方案,用于将数据管理和查询逻辑与 NoSQL 供应商特定的集成逻辑解耦。
引言
本文介绍了一个开源的 .NET 抽象层,用于访问 NoSQL 数据库,并在各种数据库之上提供类似 Entity-Framework 的存储库模式。
背景
尽管 NoSQL 变得越来越普遍,但缺乏一个抽象层来处理特定数据库的 API,从而将供应商特定的逻辑与数据库访问和管理逻辑分开。
使用代码
您可以从 CodePlex 项目站点 下载包含源代码和测试的整个解决方案,或者使用 NuGet 引入二进制文件(请参阅 CodePlex 项目站点中的链接或搜索 PubComp.NoSql)。
API
基本用法
对于熟悉 Entity Framework 等 ORM 的人来说,这样的代码会很熟悉
using (var context = new MyContext(ParametersForTests.ConnectionInfo))
{
var aaaa = new MyEntity
{
Id = Guid.NewGuid(),
Name = "aaaa",
};
var bbbb = new MyEntity
{
Id = Guid.NewGuid(),
Name = "bbbb",
};
context.MyEntities.Add(aaaa);
context.MyEntities.Add(bbbb);
}
这样的查询也不会让您感到意外
using (var context = new MyContext(ParametersForTests.ConnectionInfo))
{
var aaaa = context.MyEntities.AsQueryable().Where(ent => ent.Name == "aaaa")
.FirstOrDefault();
var bbbb = context.MyEntities.AsQueryable().Where(ent => ent.Name == "bbbb")
.FirstOrDefault();
}
定义上下文
定义一个类似这样的上下文接口可能不是您习惯的,但应该对您来说是合理的
public interface IMyContext : IDomainContext
{
IEntitySet<Guid, MyEntity> MyEntities { get; }
IEntitySet<String, UserLogin> UserLogins { get; }
new IFileSet<Guid> Files { get; }
}
实际上下文的定义可能比您习惯的要简洁一些
public class MyContext: MongoDbContext, IMyContext
{
public MyContext(MongoDbConnectionInfo connectionInfo)
: base(connectionInfo)
{
}
public IEntitySet<Guid, MyEntity>MyEntities
{
get;
private set;
}
public IEntitySet<String, UserLogin> UserLogins
{
get;
private set;
}
public new IFileSet<Guid> Files
{
get;
private set;
}
}
您不必编写代码或 XML,甚至不必在图表中选择表和列来告诉 ORM 这些实体如何映射到表,这可能会让您松一口气。由于 NoSQL 会将您的对象原封不动地序列化到数据库中,不需要模式,因此模型是您唯一需要修改的东西。
由于集合可以存储在其父实体下,作为对象列表或 ID 列表,因此不需要等同于多对多表的结构,因此,对于常用情况,单个字段足以用于 ID 属性。已测试支持基本 ID 类型包括 Guid、String 和 int。
索引呢?
您可以在上下文类中定义索引,如下所示
private static IndexDefinition MyEntityByName
{
get
{
return new IndexDefinition(
typeof(MyEntity),
new[] { new KeyProperty("Name", Direction.Ascending) },
asUnique: false,
asSparse: true);
}
}
之所以将它们定义在上下文而不是实体上,是因为上下文可以包含任何继承实体类型,并且索引可能在不存在于基类型上的属性上,而且我不想在基类型上定义指向可能只存在于继承类型上的属性的索引。
在 NoSQL 供应商之间切换
您需要做的唯一更改,即可从 MongoDB 切换到 Redis 再切换回来,就是像这样更改上下文的基类和连接信息类型
public class MyContext: MongoDbContext, IMyContext
{
public MyContext(MongoDbConnectionInfo connectionInfo)
: base(connectionInfo)
{
}
vs.
public class MyContext: RedisDbContext, IMyContext
{
public MyContext(RedisDbConnectionInfo connectionInfo)
: base(connectionInfo)
{
}
基类处理所有数据库特定的集成代码。
NoSQL 数据建模
当您设计 NoSQL 模型时,有两种类型的实体
- 一级实体 - 具有自己集合(等同于表)的实体
- 二级实体 - 包含在其他实体下的实体,没有自己的集合。
一级实体类似于您习惯映射到关系数据库的实体,但是,它们的属性可以是简单字段,也可以是复杂属性,例如另一个对象、一个集合或一个对象集合。因此,实体可以存储为其他实体的 ID 或实际对象。您可以使用这些经验法则来确定使用哪种建模方式。
- 子实体的实例是否可以独立存在(一级)还是只在父实体下才有意义(二级)?
- 子实体是否与父类具有相同的访问规则?(安全考虑。)如果不是,它们应该是一级实体。
- 当您加载父实体时,是否总是希望获取子实体(二级),还是希望能够加载它们而不加载子实体(一级)?
当您将子实体建模为一级实体时,您最终会在一个实体中包含另一个实体的 ID(类似于关系数据库中的外键),这可能会让您寻找导航属性的解决方案。
导航属性
您可以像这样定义一个导航属性
public class EntityWithNavigation : IEntity<Guid>
{
public Guid Id { get; set; }
public String Name { get; set; }
public Guid InfoId { get; set; }
[Navigation("InfoId", typeof(InfoBase))]
public Info Info { get; set; }
public IEnumerable<Guid> TagIds { get; set; }
[Navigation("TagIds", typeof(Tag))]
public IEnumerable<Tag> Tags { get; set; }
}
其中 InfoBase 是 Info 的基类,也是用于定义 Info 存储的集合的类型。
并像这样使用它
uow.EntitiesWithNavigation.SaveNavigation(new[] { item }, new[] { "Info", "Tags" });
和
uow.EntitiesWithNavigation.LoadNavigation(new[] { item }, new[] { "Info", "Tags" });
您可以选择在所有实体上或仅在某些实体上(例如,仅在特定继承类型的实体上)定义要保存或加载的属性。
其他有趣的功能
使用的其他属性包括 DbIgnoreAttribute
,它使您能够阻止存储和检索特定属性。
MongoDB 版本还支持 GridFS - MongoDB 的文件存储。
关注点
虽然这个抽象层已经实现了对 MongoDB(使用 MongoDB 的官方 C# 驱动程序)和 Redis(使用 ServiceStack.Redis)的支持,但这两个数据库及其 .NET 客户端的能力不同,因此每个抽象层都有不同的限制。有关更多详细信息,请参阅 readme 文件。