Neo4j高效添加多个节点和边

Neo4j高效添加多个节点和边,neo4j,neo4jclient,Neo4j,Neo4jclient,我有下面的例子 我想知道在单个事务中添加节点和边列表的最佳和最快的方法是什么?我使用标准的C#Neo4j.NET软件包,但是我已经读到了Neo4jClient,它更快。老实说,任何支持.NET和4.5的东西 我有一个大约60000个FooA对象的列表,需要添加到Neo4j中,这可能需要几个小时 首先,FooB对象几乎不会改变,所以我不必每天添加它们。性能问题在于每天添加两次新的FooA对象 每个FooA对象都有一个FooB对象列表,其中有两个列表包含我需要添加的关系;RelA和RelB(见下文

我有下面的例子

我想知道在单个事务中添加节点和边列表的最佳和最快的方法是什么?我使用标准的C#Neo4j.NET软件包,但是我已经读到了Neo4jClient,它更快。老实说,任何支持.NET和4.5的东西

我有一个大约60000个FooA对象的列表,需要添加到Neo4j中,这可能需要几个小时

首先,FooB对象几乎不会改变,所以我不必每天添加它们。性能问题在于每天添加两次新的FooA对象

每个FooA对象都有一个FooB对象列表,其中有两个列表包含我需要添加的关系;RelA和RelB(见下文)

如何使用Neo4jClient和UNWIND或CSV导入之外的任何其他方式添加FooA的列表


希望这是有意义的,谢谢

最大的问题是嵌套列表,这意味着您必须执行
foreach
循环,因此每个
FooA
至少执行4个查询,这对于60000个用户来说——好吧——太多了

快速注释:索引 首先也是最重要的-您需要对
FooA
FooB
节点的
Id
属性建立索引,这将大大加快查询速度

我已经玩了一点,让它在我老化的计算机上存储60000个FooA条目,并在大约12-15秒内创建96000个RelB实例

解决方案 我将其分为两个部分-FooA和RelB:

福阿 我必须将
FooA
类标准化为我可以在
Neo4jClient
中使用的东西,所以让我们介绍一下:

public class CypherableFooA
{
    public CypherableFooA(FooA fooA){
        Id = fooA.Id;
        Name = fooA.Name;
        Age = fooA.Age;
    }
    
    public long Id { get; set; }
    public string Name { get; set; }
    public long Age { get; set; }
    
    public string RelA_Val1 {get;set;}
    public long RelA_FooBId {get;set;}
}
我已经添加了
RelA_Val1
RelA_FooBId
属性,以便能够在
UNWIND
中访问它们。我使用助手方法转换您的
FooA

public static IList<CypherableFooA> ConvertToCypherable(FooA fooA){
    var output = new List<CypherableFooA>();

    foreach (var element in fooA.ListA)
    {
        var cfa = new CypherableFooA(fooA);
        cfa.RelA_FooBId = element.Node.Id;
        cfa.RelA_Val1 = element.Val1;
        output.Add(cfa);
    }
    
    return output;
}
展平
FooA
实例,因此对于
FooA
ListA
属性中的每个项目,我最终得到1个
CypherableFooA
。e、 g.如果您在每个
FooA
上的
ListA
中有2个项目,并且您有5000个
FooA
实例-您将得到包含10000个项目的
cyperable

现在,使用
cypherable
我调用我的
AddFooAs
方法:

public static void AddFooAs(IGraphClient gc, IList<CypherableFooA> fooAs, int batchSize = 10000, int startPoint = 0)
{
    var batch = fooAs.Skip(startPoint).Take(batchSize).ToList();
    Console.WriteLine($"FOOA--> {startPoint} to {batchSize + startPoint} (of {fooAs.Count}) = {batch.Count}");
    
    if (batch.Count == 0)
        return;

    gc.Cypher
        .Unwind(batch, "faItem")
        .Merge("(fa:FooA {Id: faItem.Id})")
        .OnCreate().Set("fa = faItem")
        .Merge("(fb:FooB {Id: faItem.RelA_FooBId})")
        .Create("(fa)-[:RelA {Prop: faItem.RelA_Val1}]->(fb)")
        .ExecuteWithoutResults();
    
    AddFooAs(gc, fooAs, batchSize, startPoint + batch.Count);
}
然后我将它们添加到Neo4j中,如下所示:

public static void AddRelBs(IGraphClient gc, IList<RelB> relbs, int batchSize = 10000, int startPoint = 0)
{
    var batch = relbs.Select(r => new { StartId = r.Start.Id, EndId = r.End.Id, r.ValExample }).Skip(startPoint).Take(batchSize).ToList();
    Console.WriteLine($"RELB--> {startPoint} to {batchSize + startPoint} (of {relbs.Count}) = {batch.Count}");
    if(batch.Count == 0)
        return;

    var query = gc.Cypher
        .Unwind(batch, "rbItem")
        .Match("(fb1:FooB {Id: rbItem.StartId}),(fb2:FooB {Id: rbItem.EndId})")
        .Create("(fb1)-[:RelA {Prop: rbItem.ValExample}]->(fb2)");

    query.ExecuteWithoutResults();
    AddRelBs(gc, relbs, batchSize, startPoint + batch.Count);
}
publicstaticvoidaddrelbs(IGraphClient-gc,IList-relbs,int-batchSize=10000,int-startPoint=0)
{
var batch=relbs.Select(r=>new{StartId=r.Start.Id,EndId=r.End.Id,r.ValExample}).Skip(startPoint.Take(batchSize.ToList();
WriteLine($“RELB-->{startPoint}到{batchSize+startPoint}(of{relbs.Count})={batch.Count}”);
如果(batch.Count==0)
返回;
var query=gc.Cypher
.展开(批次,“rbItem”)
.Match((fb1:FooB{Id:rbItem.StartId}),(fb2:FooB{Id:rbItem.EndId})
.Create(“(fb1)-[:RelA{Prop:rbItem.ValExample}]->(fb2)”;
query.ExecuteWithoutResults();
AddRelBs(gc、relbs、batchSize、startPoint+batch.Count);
}
同样,批处理默认为10000


显然,时间会根据
ListB
ListA
中的rel数量而有所不同-我的测试在
ListA
中有一个项目,在
ListB
中有两个项目。您对此有任何限制吗?i、 e.“Id”是否被约束为唯一的?Id是唯一的约束。
var cypherable = fooAList.SelectMany(a => ConvertToCypherable(a)).ToList();
public static void AddFooAs(IGraphClient gc, IList<CypherableFooA> fooAs, int batchSize = 10000, int startPoint = 0)
{
    var batch = fooAs.Skip(startPoint).Take(batchSize).ToList();
    Console.WriteLine($"FOOA--> {startPoint} to {batchSize + startPoint} (of {fooAs.Count}) = {batch.Count}");
    
    if (batch.Count == 0)
        return;

    gc.Cypher
        .Unwind(batch, "faItem")
        .Merge("(fa:FooA {Id: faItem.Id})")
        .OnCreate().Set("fa = faItem")
        .Merge("(fb:FooB {Id: faItem.RelA_FooBId})")
        .Create("(fa)-[:RelA {Prop: faItem.RelA_Val1}]->(fb)")
        .ExecuteWithoutResults();
    
    AddFooAs(gc, fooAs, batchSize, startPoint + batch.Count);
}
var relBs = fooAList.SelectMany(a => a.ListB.Select(lb => lb));
public static void AddRelBs(IGraphClient gc, IList<RelB> relbs, int batchSize = 10000, int startPoint = 0)
{
    var batch = relbs.Select(r => new { StartId = r.Start.Id, EndId = r.End.Id, r.ValExample }).Skip(startPoint).Take(batchSize).ToList();
    Console.WriteLine($"RELB--> {startPoint} to {batchSize + startPoint} (of {relbs.Count}) = {batch.Count}");
    if(batch.Count == 0)
        return;

    var query = gc.Cypher
        .Unwind(batch, "rbItem")
        .Match("(fb1:FooB {Id: rbItem.StartId}),(fb2:FooB {Id: rbItem.EndId})")
        .Create("(fb1)-[:RelA {Prop: rbItem.ValExample}]->(fb2)");

    query.ExecuteWithoutResults();
    AddRelBs(gc, relbs, batchSize, startPoint + batch.Count);
}