【问题标题】:c# class graph to Neo4jc# 类图到 Neo4j
【发布时间】:2018-08-13 13:25:59
【问题描述】:

我希望将内存中的普通旧 C# 类转换为 neo4j 数据库。 (类类型是节点类型并派生自,节点具有“linkedTo”的列表)

与其编写一长串密码查询来创建节点和属性,然后将它们与关系联系起来,我想知道是否有什么更聪明的方法可以做。

例如,我可以将它们序列化为 json,然后将其直接导入 neo4j 吗? 我知道 C# neo4j 驱动程序中的 .unwind 函数在这里可能会有所帮助,但没有看到很好的使用示例,然后需要单独匹配和创建关系

有没有最佳的方法来做到这一点?我预计会有大约 50k 个节点

【问题讨论】:

  • 你最终会以某种方式按摩你的课程以进入数据库 - 你是绑定到 Neo4j.Driver 还是可以使用 Neo4jClient
  • 乐于使用任一路线,只是寻找干净的路线

标签: c# neo4j neo4jclient


【解决方案1】:

好的,首先,我为此使用 Neo4jClient,并使用以下方法将INDEX 添加到数据库中:

CREATE INDEX ON :MyClass(Id)

这对于它的工作方式很重要,因为它可以更快地插入数据。

我有一堂课:

public class MyClass
{
    public int Id {get;set;}
    public string AValue {get;set;}
    public ICollection<int> LinkToIds {get;set;} = new List<int>();
}

其中有一个Id,我将关闭它,还有一个string 属性 - 只是因为。 LinkToIds 属性是此实例链接到的 Id 集合。

为了生成我的MyClass 实例,我使用这种方法随机生成它们:

private static ICollection<MyClass> GenerateMyClass(int number = 50000){
    var output = new List<MyClass>();

    Random r = new Random((int) DateTime.Now.Ticks);

    for (int i = 0; i < number; i++)
    {
        var mc = new MyClass { Id = i, AValue = $"Value_{i}" };
        var numberOfLinks = r.Next(1, 10);  
        for(int j = 0; j < numberOfLinks; j++){
            var link = r.Next(0, number-1);
            if(!mc.LinkToIds.Contains(link) && link != mc.Id)
                mc.LinkToIds.Add(link);
        }
        output.Add(mc);
    }

    return output;
}

然后我使用另一种方法将其拆分为更小的“批次”:

private static ICollection<ICollection<MyClass>> GetBatches(ICollection<MyClass> toBatch, int sizeOfBatch)
{
    var output = new List<ICollection<MyClass>>();

    if(sizeOfBatch > toBatch.Count) sizeOfBatch = toBatch.Count;

    var numBatches = toBatch.Count / sizeOfBatch;
    for(int i = 0; i < numBatches; i++){
        output.Add(toBatch.Skip(i * sizeOfBatch).Take(sizeOfBatch).ToList());
    }

    return output;
}

然后实际添加到数据库中:

void Main()
{
    var gc = new GraphClient(new Uri("http://localhost:7474/db/data"), "neo4j", "neo");
    gc.Connect();

    var batches = GetBatches(GenerateMyClass(), 5000);

    var now = DateTime.Now;
    foreach (var batch in batches)
    {
        DateTime bstart = DateTime.Now;
        var query = gc.Cypher
            .Unwind(batch, "node")
            .Merge($"(n:{nameof(MyClass)} {{Id: node.Id}})")
            .Set("n = node")
            .With("n, node")
            .Unwind("node.LinkToIds", "linkTo")
            .Merge($"(n1:{nameof(MyClass)} {{Id: linkTo}})")
            .With("n, n1")
            .Merge("(n)-[:LINKED_TO]->(n1)");

        query.ExecuteWithoutResults();
        Console.WriteLine($"Batch took: {(DateTime.Now - bstart).TotalMilliseconds} ms");
    }
    Console.WriteLine($"Total took: {(DateTime.Now - now).TotalMilliseconds} ms");
}

在我老化(现在 5-6 岁)的机器上,大约需要 20 秒才能将 50,000 个节点放入大约 500,000 个关系中。

让我们打破上面对 Neo4j 的重要调用。关键是您正确地建议UNWIND - 这里我UNWIND 一个批次,并为该集合中的每个“行”提供node 的标识符。然后我可以访问属性 (node.Id) 并将其用于MERGE 一个节点。在第一次展开时 - 我总是将 SET 新创建的节点 (n) 设置为 node,因此设置了所有属性(在本例中为 AValue)。

所以直到第一个With,我们已经创建了一个带有MyClass 标签的新节点,并设置了所有属性。现在。这确实包括有一个 LinkToIds 数组,如果你是一个整洁的人 - 你可能想要删除它。我会把它留给你自己。

在第二个UNWIND 中,我们利用LinkToIds 属性是一个数组这一事实,并使用它来创建一个稍后将填充的“占位符”节点,然后我们在@987654345 之间创建一个关系@ 和 n1 占位符。注意 - 如果我们已经创建了一个与n1 具有相同 ID 的节点,我们将使用该节点,并且当我们在第一个 UNWIND 期间获得相同的 ID 时,我们将设置占位符的所有属性。

这不是最容易解释的,但最好看的是 Neo4j 文档中的 MERGEUNWIND

【讨论】:

  • 另外 - 我没有'异步'd它,或者使用螺栓..:/
  • 哇,谢谢 Chris,看起来很棒,我很高兴能尝试一下,而且比我预期的要快得多。
  • 我知道如何修改它以制作不同类型的边缘(通过更改 :LINKED_TO),我知道在 neo4j 中可以有边缘的属性,可以将这些设置为相同的查询?
  • 'AValue' 属性在.Set("n = node") 行中被提取——因为这只是将node 的值复制到n 中。对于另一个问题 - 是的,你可以,就像.Set("n = node")一样 - 只是你使用r而不是r或任何你选择定义你的关系的标识符,在上面的例子中我已经没有给出标识符,但它只是:之前的东西,比如:.Merge("(n)-[r:LINKED_TO]-&gt;(n1)").Set("r.IsCurrent = true")
猜你喜欢
  • 2013-09-29
  • 1970-01-01
  • 1970-01-01
  • 2020-02-10
  • 2013-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-20
相关资源
最近更新 更多