试用EF Core Azure Cosmos DB Provider教程

110 阅读5分钟

EF Core 6.0的发布就在眼前(当我写这篇文章时)。该团队一直在努力工作,增加功能。一个重点领域是Azure Cosmos DB体验。我们收到的反馈是,许多开发者更愿意使用Cosmos DB的提供商,但他们在等待某些关键功能。

行星文档

我建立了一个参考应用,它在Blazor服务器上使用Azure Cosmos DB与EF Core。它包括搜索功能、交叉引用的实体,以及创建、读取和更新的界面。我最近升级到了最新的EF Core 6.0版本,并且能够简化和删除相当多的代码!

Screenshot of Planetary Docs

功能概述

以下是我们在EF Core 6.0 Azure Cosmos DB提供商中加入的一些要求的功能。

隐式所有权

EF Core是作为一个对象关系映射器建立的。在关系型数据库中,复杂的关系是通过将相关实体存储在不同的表中,并用外键来引用它们来表达的。EF Core假设在父体中遇到的非原始实体类型被表达为外键关系。这些关系使用HasManyHasOne 进行配置,并且假定这些实例独立存在,有配置的关系。在文档数据库中,实体类型的默认行为是假定它们是由父类拥有的嵌入式文档。换句话说,复杂类型的数据存在于父类的上下文中。在以前的EF Core版本中,必须明确配置这种行为,才能与Azure Cosmos DB提供商一起工作。在EF Core 6.0中,所有权是隐含的。这节省了配置,并确保该行为与其他供应商的NoSQL方法一致。

例如,在Planetary Docs中,有作者和标签。这些实体 "拥有 "一个指向相关文档的URL和标题的摘要列表。这样,当用户问 "哪些文档有标签X "时,我只需要加载一个文档来回答这个问题(我加载标签X,然后遍历其拥有的标题集合)。使用EF Core 5,我必须明确地要求所有权:

tagModel.OwnsMany(t => t.Documents);
authorModel.OwnsMany(t => t.Documents);

在EF Core 6中,所有权是隐式的,所以除了指定分区键,不需要配置实体。

对原始集合的支持

在关系型数据库中,原始集合的建模方式通常是将其提升为复杂类型,或者将其转换为序列化的工件,以存储在单一列中。考虑一下一个可以有标签列表的博客文章。一种常见的方法是创建一个代表标签的实体:

public class Tag 
{
    public int Id { get; set; }
    public string Text { get; set; }
}

然后引用该标签:

public ICollection<Tag> Tags { get; set; }

基元被提升到一个复杂的类型,并存储在一个单独的表中。另一种方法是将标签折叠成一个包含以逗号分隔的列表的单一字段。这种方法需要一个值转换器,以便将列表纳入字段进行更新,并将字段分解为列表进行读取。这也使得回答诸如 "有多少帖子被标记为X?"这样的问题变得困难和昂贵。 使用EF Core 5,我选择了单列方法。我在编写时将列表序列化为JSON,在读取时反序列化。这就是序列化的代码:

private static string ToJson<T>(T item) => JsonSerializer.Serialize(item);
private static T FromJson<T>(string json) => JsonSerializer.Deserialize<T>(json);

我配置了EF Core来进行转换:

docModel.Property(d => d.Tags)
    .HasConversion(
        t => ToJson(t),
        t => FromJson<List<string>>(t));

结果文件看起来是这样的:

{
    "tags" : "[\"one\", \"two\", \"three\"]"
}

在EF Core 6.0中,我简单地删除了这些代码,以利用内置的原始类型的处理。这导致了一个像这样的文档:

{
    "tags" : [ 
        "one",
        "two",
        "three"
    ]
}

这就导致了Azure Cosmos DB在处理模式上没有问题。另一方面,当使用标签作为数组的当前模型遇到使用标签作为字段的传统记录时,C#代码会抛出。当EF Core没有NoSQL迁移的概念时,我们该如何处理?

原始SQL

一个流行的要求是允许开发者为数据访问编写自己的SQL。这正是我在处理代码迁移时需要的功能。为了使原始SQL发挥作用,它必须投射到一个现有的模型上。它是实体的DbSet<T> 的一个扩展。在我的案例中,它实现了就地迁移。在更新了代码后,试图加载一个文档会失败。文档中的 "标签 "只有一个字符串属性,但C#模型是一个数组,所以JSON序列化器会抛出一个异常。为了解决这个问题,我使用了Azure Cosmos DB的一个内置功能,将字符串解析为数组。使用查询,我把实体投射到与当前模式相匹配的文档中,然后再把它保存回来。这就是迁移的代码:

var docs = await Documents.FromSqlRaw(
    "select c.id, c.Uid, c.AuthorAlias, c.Description, c.Html, c.Markdown, c.PublishDate, c.Title, STRINGTOARRAY(c.Tags) as Tags from c").ToListAsync();
foreach (var doc in docs)
{
    Entry(doc).State = EntityState.Modified;
}

这个功能使开发人员能够制作复杂的查询,而LINQ提供者可能不支持这些查询。

额外的增强功能

除了我已经介绍过的内容外,这些增强功能也进入了:

总结

我对即将到来的变化感到兴奋,希望你也是如此。你在使用Cosmos DB提供者吗?既然我们已经添加了这些功能,你是否考虑使用?是否有一些你需要的关键功能被我们遗漏了?请在下面的评论中告诉我。谢谢你!。