Самая большая проблема - это вложенные списки, что означает, что вы должны выполнить свои циклы foreach
, поэтому вы в конечном итоге выполните минимум из 4 запросов за FooA
,что для 60 000 - ну, это много!
Краткое примечание RE: Индексирование
Прежде всего - вам нужен индекс для свойства Id
ваших FooA
и FooB
узлы, это значительно ускорит ваши запросы.
Я немного поиграл с этим, сохранив 60 000 записей FooA и создав 96 000 экземпляров RelB примерно за 12-15 секунд на моем устаревшем компьютере.
Решение
Я разделил его на 2 секции - FooA и RelB:
FooA
Мне пришлось нормализовать класс 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;
}
Это в сочетании с:
var cypherable = fooAList.SelectMany(a => ConvertToCypherable(a)).ToList();
Сглаживает экземпляры FooA
, поэтому я получаю 1 CypherableFooA
для каждый элемент в ListA
свойстве FooA
.например, если у вас было 2 элемента в ListA
на каждые FooA
, и у вас есть 5000 FooA
экземпляров - в итоге вы получите cypherable
, содержащий 10 000 элементов.
Теперь с cypherable
я звонюmy AddFooAs
method:
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);
}
Это группирует запрос в пакеты по 10 000 (по умолчанию) - у меня это занимает около 5-6 секунд - примерно так же, как если бы я пробовал все 60 000 за один раз.
RelB
Вы сохраняете RelB
в вашем примере с FooA
, но в написанном вами запросе вообще не используется FooA
, так чтоГотово - это извлечь и сгладить все RelB
экземпляры в свойстве ListB
:
var relBs = fooAList.SelectMany(a => a.ListB.Select(lb => lb));
Затем я добавляю их в 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);
}
Снова пакетная обработка по умолчанию10000.
Очевидно, что время будет варьироваться в зависимости от количества rel в ListB
и ListA
- Мои тесты имеют один элемент в ListA
и 2 в ListB
.