1
头图

EF Core 6.0在今年的11月已经正式发布了。团队的成员们一直在努力添加并完善功能。其中一个重点领域是Azure Cosmos DB体验。我们收到的反馈是,许多开发人员更愿意使用Cosmos DB,但仍在等待某些关键功能。

行星文档

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

ae9f6503cd125a08da226dc24fafc4b5.png

功能概述

以下是我们在EF Core 6.0 Azure Cosmos DB provider中添加的一些特性。

隐式的所有权

EF Core被构建成一个对象关系映射器。在关系数据库中,复杂的关系是通过将相关实体存储在单独的表中并使用外键引用它们来表示的。EF Core假定在父类中遇到的非实体类型被表示为外键关系。使用HasMany或HasOne配置关系,并且假设实例与配置的关系独立存在。在文档数据库中,实体类型的默认行为是假定它们是父类所拥有的嵌入式文档。换句话说,复杂类型的数据存在于父类型的上下文中。在早期版本的EF Core中,必须显式地配置此行为,使其能够与Azure Cosmos DB provider一起工作。在EF Core 6.0中,所有权是隐性的。这将保存配置并确保行为与其他提供者的NoSQL方法一致。

例如,在行星文档中有作者和标签。这些实体“拥有”一个指向URL和相关文档标题的摘要列表。这样,当用户问“什么文档有标签X”时,我只需要加载一个文档来回答这个问题(我加载标签X,然后迭代它拥有的标题集合)。使用EF Core5,我必须明确声明所有权:

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"
    ]
}

这导致了schema发现改变,但是Azure Cosmos DB没有问题处理。另一方面,当使用标签作为数组的当前模型遇到使用标签作为字段的旧记录时,c#代码将抛出异常。当EF Core没有NoSQL迁移的概念时,我们如何处理这个问题?

Raw SQL

一个很常见的请求是允许开发人员为数据访问编写自己的SQL。这正是我处理代码迁移所需要的特性。要使Raw SQL工作,它必须投射到一个现有的模型。它是实体的DbSet<T>的扩展。在我的例子中,它支持就地迁移。在更新代码之后,尝试加载文档将会失败。文档只有一个字符串属性用于“tag”,但c#模型是一个数组,因此JSON序列化会抛出一个异常。为了解决这个问题,我使用了Azure Cosmos DB的一个内置特性,它将字符串解析为数组。使用查询,我将实体投影到匹配当前schema的文档中,然后将其保存回来。这是迁移代码:

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 provider可能不支持的复杂查询。

其他增强功能

除了我已经介绍过的内容之外,这些增强功能也包含在其中。

总结

我对即将到来的变化感到兴奋,希望你们也一样。你正在使用Cosmos DB provider吗? 如果还没有,在我们添加了以上功能之后你愿意使用吗? 或者还有更多需求和建议,请在本文下面留言,谢谢!


欢迎关注微软中国MSDN订阅号,获取更多最新发布!
image.png


微软技术栈
423 声望996 粉丝

微软技术生态官方平台。予力众生,成就不凡!微软致力于用技术改变世界,助力企业实现数字化转型。