在此之前,将使用官方驱动程序执行的 Cypher 查询结果转换为对象的过程一直是一件费力的事情。它涉及一遍又一遍地编写同一行代码,每次都进行小的更改,以将记录的各个部分映射到对象的属性。.NET 驱动程序中的一项新功能(目前处于预览状态)允许简单地定义此映射,或者在大多数情况下自动完成。
Neo4j 的 .NET 驱动程序中添加了一项功能,允许从查询结果快速构造 C# 对象。如果未提供自定义配置,则映射是按约定完成的,其中属性或构造函数参数的名称用于决定要使用记录中的哪些数据。但是,也可以精确地指定如何从记录映射到对象类型。有了这两个选项,再加上在两者之间的某个位置配置映射的能力,所有记录到对象的映射方案都应该是可行的。
假设我们有以下 Cypher 查询:
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)<-[:ACTED_IN]-(c:Person)
WHERE id(p) <> id(c)
RETURN p AS person, m AS movie, COLLECT(c) AS costars
我们将把每条记录加载到 C# 对象中,如下所示:
public class ActingJob
{
public Person Person { get; set; }
public Movie Movie { get; set; }
public List<Person> Costars { get; set; }
}
public class Movie
{
public string Title { get; set; } = “”;
public int Released { get; set; }
public string? Tagline { get; set; }
}
public class Person
{
public string Name { get; set; } = “”;
public int? Born { get; set; }
}
结果中的每条记录都将生成一个实例,其中包含从查询结果中填充的属性。ActingJob
目前,要做到这一点,你可以写如下内容:
var (results, _) = await driver
.ExecutableQuery(cypherQuery)
.ExecuteAsync();
var actingJobs = new List<ActingJob>();
foreach (var record in results)
{
var person = new Person
{
Name = record[“person”].As<INode>().Properties[“name”].As<string>(),
Born = record[“person”].As<INode>().Properties.TryGetValue(“born”, out var born) ? born.As<int>() : null
};
var movie = new Movie
{
Title = record[“movie”].As<INode>().Properties[“title”].As<string>(),
Released = record[“movie”].As<INode>().Properties[“released”].As<int>(),
Tagline = record[“movie”].As<INode>().Properties.TryGetValue(“tagline”, out var tl) ? tl.As<string>() : “”
};
var costars = record[“costars”].As<IReadOnlyList<INode>>()
.Select(node => new Person
{
Name = node.Properties[“name”].As<string>(),
Born = node.Properties.TryGetValue(“born”, out var born) ? born.As<int>() : null
})
.ToList();
var job = new ActingJob
{
Person = person,
Movie = movie,
Costars = costars
};
actingJobs.Add(job);
}
使用新的映射功能,使用以下代码将获得完全相同的结果:
using Neo4j.Driver.Preview.Mapping; // enable preview mapping features
// …
var actingJobs = await driver
.ExecutableQuery(cypherQuery)
.ExecuteAsync()
.AsObjectsAsync\<ActingJob>();
通过检查对象中属性的名称和类型并将它们与记录中的字段进行匹配,将自动完成所有相同的映射。
此示例使用默认映射器,这是未定义映射配置时使用的方法。我们希望它适用于绝大多数映射方案。可以给默认映射器“提示”,这进一步扩展了所涵盖的情况。
例如,如果类中的属性要重命名为 ,我们仍然可以通过用属性修饰它来使用默认映射器,如下所示:BornPersonBirthYear[MappingSource]
[MappingSource(“born”)]
public int BirthYear { get; set; }
有关可用提示的完整信息,请参阅本文末尾的链接。
可能出现的另一种情况是,当一个类将其所有属性声明为只读并从构造函数设置它们时,如下面的示例所示(请注意,此代码使用的是 C# 12 中的新主构造函数语法):
public class Movie (string title, int released, string? tagline)
{
public string Title => title;
public int Released => released;
public string? Tagline => tagline;
}
在这种情况下,默认映射器将查看构造函数中的参数名称,并以相同的方式从记录中映射。如果无法执行此操作,则会引发异常。可用于属性的相同映射属性也可以与构造函数参数一起使用。
除了允许映射驱动程序级查询执行结果的方法外,还有一些扩展方法允许你在其他方案中利用映射功能。AsObjectsAsync
如果有 ,则可以使用该方法返回映射对象的列表。或者,如果您有一个 ,则可以使用该方法将其映射到对象。这些方法将使用默认映射器或已定义的任何自定义映射。IResultCursorToListAsync<T>IRecordAsObject<T>
如果出现默认映射器无法执行所需操作的情况,则有两种方法可以创建自定义映射。第一种方法是创建一个实现接口的类,其中 T 是要映射到的类型(例如 )。此接口将允许您使用现有的样板代码并编写从 an 映射到 .将此代码放入其自己的类中的优点是,你可以使用驱动程序的映射功能,并且你的自定义映射将始终在可用时使用。IRecordMapper<T>MovieIRecordT
第二种方法是定义一个实现 .此接口允许您使用流畅的 API 逐个属性配置映射,并选择性地利用默认映射器。IMappingProvider
对这两种类型的自定义映射的完整讨论超出了本文的范围,因此请参阅末尾的链接以获取更多信息。