public override bool RelationshipExists(Property foreignProperty, OGM item) { string pattern; if (foreignProperty.Direction == DirectionEnum.In) { pattern = "MATCH (node:{0})<-[:{2}]-(:{3}) WHERE node.{1} = {{key}} RETURN node LIMIT 1"; } else { pattern = "MATCH (node:{0})-[:{2}]->(:{3}) WHERE node.{1} = {{key}} RETURN node LIMIT 1"; } string match = string.Format( pattern, item.GetEntity().Label.Name, item.GetEntity().Key.Name, foreignProperty.Relationship?.Neo4JRelationshipType, foreignProperty.Parent.Label.Name); Dictionary <string, object?> parameters = new Dictionary <string, object?>(); parameters.Add("key", item.GetKey()); var result = Transaction.RunningTransaction.Run(match, parameters); return(result.Any()); }
protected virtual void Add(Transaction trans, Relationship relationship, OGM inItem, OGM outItem) { string match = string.Format("MATCH (in:{0}) WHERE in.{1} = $inKey \r\n MATCH (out:{2}) WHERE out.{3} = $outKey", inItem.GetEntity().Label.Name, inItem.GetEntity().Key.Name, outItem.GetEntity().Label.Name, outItem.GetEntity().Key.Name); string create = string.Format("MERGE (in)-[outr:{0}]->(out) ON CREATE SET outr.CreationDate = ${1} SET outr += $node", relationship.Neo4JRelationshipType, relationship.CreationDate); Dictionary <string, object?> parameters = new Dictionary <string, object?>(); parameters.Add("inKey", inItem.GetKey()); parameters.Add("outKey", outItem.GetKey()); Dictionary <string, object> node = new Dictionary <string, object>(); parameters.Add(relationship.CreationDate, Conversion <DateTime, long> .Convert(Transaction.RunningTransaction.TransactionDate)); parameters.Add("node", node); string query = match + "\r\n" + create; relationship.RaiseOnRelationCreate(trans); RawResult result = trans.Run(query, parameters); }
public override void RemoveUnmanaged(Relationship relationship, OGM inItem, OGM outItem, DateTime?moment) { Transaction trans = Transaction.RunningTransaction; Checks(relationship, inItem, outItem); if (relationship.IsTimeDependent == false) { throw new NotSupportedException("EndCurrentRelationship method is only supported for time dependent relationship."); } string match = string.Format( "MATCH (in:{0})-[r:{1}]->(out:{2}) WHERE in.{3} = {{inKey}} and out.{4} = {{outKey}} and COALESCE(r.{5}, {{minDateTime}}) = {{moment}} DELETE r", inItem.GetEntity().Label.Name, relationship.Neo4JRelationshipType, outItem.GetEntity().Label.Name, inItem.GetEntity().Key.Name, outItem.GetEntity().Key.Name, relationship.StartDate); Dictionary <string, object?> parameters = new Dictionary <string, object?>(); parameters.Add("inKey", inItem.GetKey()); parameters.Add("outKey", outItem.GetKey()); parameters.Add("moment", Conversion <DateTime, long> .Convert(moment ?? DateTime.MinValue)); parameters.Add("minDateTime", Conversion <DateTime, long> .Convert(DateTime.MinValue)); relationship.RaiseOnRelationDelete(trans); trans.Run(match, parameters); relationship.RaiseOnRelationDeleted(trans); }
internal void Register(OGM item) { if (item == null) { return; } item.Transaction = this; string entityName = item.GetEntity().Name; Dictionary <OGM, OGM>?values; if (!registeredEntities.TryGetValue(entityName, out values)) { values = new Dictionary <OGM, OGM>(1000); registeredEntities.Add(entityName, values); } OGM?inSet; if (values.TryGetValue(item, out inSet)) { if (inSet.PersistenceState != PersistenceState.DoesntExist && inSet == item) { throw new InvalidOperationException("You cannot register an already loaded object."); } } else { values.Add(item, item); } }
public override void Delete(OGM item) { Transaction trans = Transaction.RunningTransaction; Entity entity = item.GetEntity(); string match; Dictionary <string, object?> parameters = new Dictionary <string, object?>(); parameters.Add("key", item.GetKey()); if (entity.RowVersion == null) { match = string.Format("MATCH (node:{0}) WHERE node.{1} = {{key}} DELETE node", entity.Label.Name, entity.Key.Name); } else { parameters.Add("lockId", Conversion <DateTime, long> .Convert(item.GetRowVersion())); match = string.Format("MATCH (node:{0}) WHERE node.{1} = {{key}} AND node.{2} = {{lockId}} DELETE node", entity.Label.Name, entity.Key.Name, entity.RowVersion.Name); } Dictionary <string, object?>?customState = null; var args = entity.RaiseOnNodeDelete(trans, item, match, parameters, ref customState); RawResult result = trans.Run(args.Cypher, args.Parameters); if (result.Statistics().NodesDeleted == 0) { throw new DBConcurrencyException($"The {entity.Name} with {entity.Key.Name} '{item.GetKey()?.ToString() ?? "<NULL>"}' was changed or deleted by another process or thread."); } entity.RaiseOnNodeDeleted(trans, args); item.PersistenceState = PersistenceState.Deleted; }
static private T Activator <T>(Entity entity) { if (entity.IsAbstract) { throw new NotSupportedException($"You cannot instantiate the abstract entity {entity.Name}."); } Func <OGM> activator = activators.TryGetOrAdd(entity.Name, key => { Dictionary <string, Func <OGM> > retval = new Dictionary <string, Func <OGM> >(); foreach (Type type in typeof(T).Assembly.GetTypes()) { if (type.BaseType != null && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeof(OGM <, ,>)) { OGM instance = (OGM)System.Runtime.Serialization.FormatterServices.GetUninitializedObject(type); Entity entityInstance = instance.GetEntity(); if (entityInstance.IsAbstract) { continue; } retval.Add(entityInstance.Name, Expression.Lambda <Func <OGM> >(Expression.New(type)).Compile()); } } return(retval[entity.Name]); }); return((T)Transaction.Execute(() => activator.Invoke(), EventOptions.GraphEvents)); }
protected virtual void Remove(Transaction trans, Relationship relationship, OGM?inItem, OGM?outItem) { string cypher; Dictionary <string, object?> parameters = new Dictionary <string, object?>(); if (inItem is not null) { parameters.Add("inKey", inItem !.GetKey()); } if (outItem is not null) { parameters.Add("outKey", outItem !.GetKey()); } Entity?inEntity = inItem?.GetEntity(); Entity?outEntity = outItem?.GetEntity(); string inLabel = (inEntity is null) ? "in" : $"in:{inEntity.Label.Name} {{ {inEntity.Key.Name}: $inKey }}"; string outLabel = (outEntity is null) ? "out" : $"out:{outEntity.Label.Name} {{ {outEntity.Key.Name}: $outKey }}"; cypher = $"MATCH ({inLabel})-[r:{relationship.Neo4JRelationshipType}]->({outLabel}) DELETE r"; relationship.RaiseOnRelationDelete(trans); RawResult result = trans.Run(cypher, parameters); }
public override void RemoveAll(Relationship relationship, OGM item, DateTime?moment, bool timedependent) { Dictionary <string, object?> parameters = new Dictionary <string, object?>(); parameters.Add("key", item.GetKey()); DirectionEnum direction = relationship.ComputeDirection(item.GetEntity()); string match = (direction == DirectionEnum.Out) ? "MATCH (item:{0})<-[r:{1}]-(out)" : "MATCH (item:{0})-[r:{1}]->(out)"; Entity outEntity = (direction == DirectionEnum.Out) ? relationship.InEntity : relationship.OutEntity; string condition = string.Join(" OR ", outEntity.GetDbNames("out")); if (timedependent) { parameters.Add("moment", Conversion <DateTime, long> .Convert(moment ?? DateTime.MinValue)); // End Current string cypher = string.Format( match + " WHERE ({2}) and item.{3} = {{key}} and (r.{5} > {{moment}} OR r.{5} IS NULL) AND (r.{4} <={{moment}} OR r.{4} IS NULL) SET r.EndDate = {{moment}}", item.GetEntity().Label.Name, relationship.Neo4JRelationshipType, condition, item.GetEntity().Key.Name, relationship.StartDate, relationship.EndDate); Transaction.RunningTransaction.Run(cypher, parameters); // Remove Future cypher = string.Format( match + " WHERE ({2}) and item.{3} = {{key}} and r.{4} > {{moment}} DELETE r", item.GetEntity().Label.Name, relationship.Neo4JRelationshipType, condition, item.GetEntity().Key.Name, relationship.StartDate); Transaction.RunningTransaction.Run(cypher, parameters); //IResult result = trans.Run(cypher, parameters); //if (result.Summary.Counters.RelationshipsDeleted == 0) // throw new ApplicationException($"Unable to delete all time dependent future relationships '{relationship.Neo4JRelationshipType}' related to {item.GetEntity().Label.Name}({item.GetKey()})."); } else { string cypher = string.Format( match + " WHERE ({2}) and item.{3} = {{key}} DELETE r", item.GetEntity().Label.Name, relationship.Neo4JRelationshipType, condition, item.GetEntity().Key.Name); Transaction.RunningTransaction.Run(cypher, parameters); //IResult result = trans.Run(cypher, parameters); //if (result.Summary.Counters.RelationshipsDeleted == 0) // throw new ApplicationException($"Unable to remove all relationships '{relationship.Neo4JRelationshipType}' related to {item.GetEntity().Label.Name}({item.GetKey()})."); } }
public override void Add(Relationship relationship, OGM inItem, OGM outItem, DateTime?moment, bool timedependent) { Transaction trans = Transaction.RunningTransaction; Checks(relationship, inItem, outItem); string match = string.Format("MATCH (in:{0}) WHERE in.{1} = {{inKey}} \r\n MATCH (out:{2}) WHERE out.{3} = {{outKey}}", inItem.GetEntity().Label.Name, inItem.GetEntity().Key.Name, outItem.GetEntity().Label.Name, outItem.GetEntity().Key.Name); string create = string.Format("MERGE (in)-[outr:{0}]->(out) ON CREATE SET outr.CreationDate = {{{1}}} SET outr += {{node}}", relationship.Neo4JRelationshipType, relationship.CreationDate); Dictionary <string, object?> parameters = new Dictionary <string, object?>(); parameters.Add("inKey", inItem.GetKey()); parameters.Add("outKey", outItem.GetKey()); Dictionary <string, object> node = new Dictionary <string, object>(); parameters.Add(relationship.CreationDate, Conversion <DateTime, long> .Convert(trans.TransactionDate)); if (timedependent) { DateTime startDate = moment.HasValue ? moment.Value : DateTime.MinValue; node.Add(relationship.StartDate, Conversion <DateTime, long> .Convert(startDate)); node.Add(relationship.EndDate, Conversion <DateTime, long> .Convert(DateTime.MaxValue)); } parameters.Add("node", node); string query = match + "\r\n" + create; relationship.RaiseOnRelationCreate(trans); RawResult result = trans.Run(query, parameters); relationship.RaiseOnRelationCreated(trans); //if (result.Summary.Counters.RelationshipsCreated == 0) // throw new ApplicationException($"Unable to create relationship '{relationship.Neo4JRelationshipType}' between {inItem.GetEntity().Label.Name}({inItem.GetKey()}) and {outItem.GetEntity().Label.Name}({outItem.GetKey()})"); }
public override void Load(OGM item) { Transaction trans = Transaction.RunningTransaction; string returnStatement = " RETURN node"; string match = string.Format("MATCH (node:{0}) WHERE node.{1} = {{key}}", item.GetEntity().Label.Name, item.GetEntity().Key.Name); Dictionary <string, object?> parameters = new Dictionary <string, object?>(); parameters.Add("key", item.GetKey()); Dictionary <string, object?>?customState = null; var args = item.GetEntity().RaiseOnNodeLoading(trans, item, match + returnStatement, parameters, ref customState); var result = trans.Run(args.Cypher, args.Parameters); RawRecord record = result.FirstOrDefault(); if (record == null) { item.PersistenceState = PersistenceState.DoesntExist; return; } RawNode loaded = record["node"].As <RawNode>(); args.Id = loaded.Id; args.Labels = loaded.Labels; // HACK: To make it faster we do not copy/replicate the Dictionary here, but it means someone // could be changing the INode content from within an event. Possibly dangerous, but // turns out the Neo4j driver can deal with it ... for now ... args = item.GetEntity().RaiseOnNodeLoaded(trans, args, loaded.Id, loaded.Labels, (Dictionary <string, object?>)loaded.Properties); if (item.PersistenceState == PersistenceState.HasUid || item.PersistenceState == PersistenceState.Loaded) { item.SetData(args.Properties !); item.PersistenceState = PersistenceState.Loaded; } }
protected virtual void Remove(Transaction trans, Relationship relationship, OGM?inItem, OGM?outItem, DateTime moment) { // Expected behavior time dependent relationship: // ---------------------------------------------- // // Match existing relation where in & out item (omit the check for the item which is null, Remove will be used to execute RemoveAll) // IsAfter -> Remove relation // Overlaps -> Set relation.EndDate to "moment" Entity?inEntity = inItem?.GetEntity(); Entity?outEntity = outItem?.GetEntity(); string inLabel = (inEntity is null) ? "in" : $"in:{inEntity.Label.Name} {{ {inEntity.Key.Name}: $inKey }}"; string outLabel = (outEntity is null) ? "out" : $"out:{outEntity.Label.Name} {{ {outEntity.Key.Name}: $outKey }}"; StringBuilder sb = new StringBuilder(); sb.AppendLine($"MATCH ({inLabel})-[rel:{relationship.Neo4JRelationshipType}]->({outLabel})"); sb.AppendLine("WHERE COALESCE(rel.StartDate, $min) >= $moment"); sb.AppendLine("DELETE rel"); string delete = sb.ToString(); sb.Clear(); sb.AppendLine($"MATCH ({inLabel})-[rel:{relationship.Neo4JRelationshipType}]->({outLabel})"); sb.AppendLine("WHERE COALESCE(rel.StartDate, $min) <= $moment AND COALESCE(rel.EndDate, $max) >= $moment"); sb.AppendLine("SET rel.EndDate = $moment"); string update = sb.ToString(); Dictionary <string, object?> parameters = new Dictionary <string, object?>(); if (inItem is not null) { parameters.Add("inKey", inItem !.GetKey()); } if (outItem is not null) { parameters.Add("outKey", outItem !.GetKey()); } parameters.Add("min", Conversion.MinDateTimeInMS); parameters.Add("max", Conversion.MaxDateTimeInMS); parameters.Add("moment", Conversion <DateTime, long> .Convert(moment)); relationship.RaiseOnRelationCreate(trans); RawResult deleteResult = trans.Run(delete, parameters); RawResult updateResult = trans.Run(update, parameters); }
public override void Insert(OGM item) { Transaction trans = Transaction.RunningTransaction; Entity entity = item.GetEntity(); string labels = string.Join(":", entity.GetBaseTypesAndSelf().Where(x => x.IsVirtual == false).Select(x => x.Label.Name)); if (entity.RowVersion != null) { item.SetRowVersion(trans.TransactionDate); } IDictionary <string, object?> node = item.GetData(); string create = string.Format("CREATE (inserted:{0} {{node}}) Return inserted", labels); if (entity.FunctionalId != null) { object?key = item.GetKey(); if (key is null) { string nextKey = string.Format("CALL blueprint41.functionalid.next('{0}') YIELD value as key", entity.FunctionalId.Label); if (entity.FunctionalId.Format == IdFormat.Numeric) { nextKey = string.Format("CALL blueprint41.functionalid.nextNumeric('{0}') YIELD value as key", entity.FunctionalId.Label); } create = nextKey + "\r\n" + string.Format("CREATE (inserted:{0} {{node}}) SET inserted.{1} = key Return inserted", labels, entity.Key.Name); node.Remove(entity.Key.Name); } else { entity.FunctionalId.SeenUid(key.ToString() !); } } Dictionary <string, object?> parameters = new Dictionary <string, object?>(); parameters.Add("node", node); Dictionary <string, object?>?customState = null; var args = entity.RaiseOnNodeCreate(trans, item, create, parameters, ref customState); var result = trans.Run(args.Cypher, args.Parameters); RawRecord record = result.FirstOrDefault(); if (record == null) { throw new InvalidOperationException($"Due to an unexpected state of the neo4j transaction, it seems impossible to insert the {entity.Name} at this time."); } RawNode inserted = record["inserted"].As <RawNode>(); args.Id = inserted.Id; args.Labels = inserted.Labels; // HACK: To make it faster we do not copy/replicate the Dictionary here, but it means someone // could be changing the INode content from within an event. Possibly dangerous, but // turns out the Neo4j driver can deal with it ... for now ... args.Properties = (Dictionary <string, object?>)inserted.Properties; args = entity.RaiseOnNodeCreated(trans, args, inserted.Id, inserted.Labels, (Dictionary <string, object?>)inserted.Properties); item.SetData(args.Properties !); item.PersistenceState = PersistenceState.Persisted; Transaction.RunningTransaction.Register(entity.Name, item, true); }
public override void AddUnmanaged(Relationship relationship, OGM inItem, OGM outItem, DateTime?startDate, DateTime?endDate, bool fullyUnmanaged = false) { Transaction trans = Transaction.RunningTransaction; Checks(relationship, inItem, outItem); if (!fullyUnmanaged) { string find = string.Format( "MATCH (in:{0})-[r:{1}]->(out:{2}) WHERE in.{3} = {{inKey}} and out.{4} = {{outKey}} and (r.{5} <= {{endDate}} OR r.{5} IS NULL) AND (r.{6} > {{startDate}} OR r.{6} IS NULL) RETURN min(COALESCE(r.{5}, {{MinDateTime}})) as MinStartDate, max(COALESCE(r.{6}, {{MaxDateTime}})) as MaxEndDate, count(r) as Count", inItem.GetEntity().Label.Name, relationship.Neo4JRelationshipType, outItem.GetEntity().Label.Name, inItem.GetEntity().Key.Name, outItem.GetEntity().Key.Name, relationship.StartDate, relationship.EndDate); Dictionary <string, object?> parameters = new Dictionary <string, object?>(); parameters.Add("inKey", inItem.GetKey()); parameters.Add("outKey", outItem.GetKey()); parameters.Add("startDate", Conversion <DateTime, long> .Convert(startDate ?? DateTime.MinValue)); parameters.Add("endDate", Conversion <DateTime, long> .Convert(endDate ?? DateTime.MaxValue)); parameters.Add("MinDateTime", Conversion <DateTime, long> .Convert(DateTime.MinValue)); parameters.Add("MaxDateTime", Conversion <DateTime, long> .Convert(DateTime.MaxValue)); RawResult result = trans.Run(find, parameters); RawRecord record = result.FirstOrDefault(); int count = record["Count"].As <int>(); if (count > 0) { DateTime?minStartDate = Conversion <long?, DateTime?> .Convert(record["MinStartDate"].As <long?>()); DateTime?maxEndDate = Conversion <long?, DateTime?> .Convert(record["MaxEndDate"].As <long?>()); if (startDate > minStartDate) { startDate = minStartDate ?? DateTime.MinValue; } if (endDate < maxEndDate) { endDate = maxEndDate ?? DateTime.MaxValue; } } string delete = string.Format( "MATCH (in:{0})-[r:{1}]->(out:{2}) WHERE in.{3} = {{inKey}} and out.{4} = {{outKey}} and (r.{5} <= {{endDate}}) AND (r.{6} > {{startDate}}) DELETE r", inItem.GetEntity().Label.Name, relationship.Neo4JRelationshipType, outItem.GetEntity().Label.Name, inItem.GetEntity().Key.Name, outItem.GetEntity().Key.Name, relationship.StartDate, relationship.EndDate); trans.Run(delete, parameters); } string match = string.Format("MATCH (in:{0}) WHERE in.{1} = {{inKey}} MATCH (out:{2}) WHERE out.{3} = {{outKey}}", inItem.GetEntity().Label.Name, inItem.GetEntity().Key.Name, outItem.GetEntity().Label.Name, outItem.GetEntity().Key.Name); string create = string.Format("CREATE (in)-[outr:{0} {{node}}]->(out)", relationship.Neo4JRelationshipType); Dictionary <string, object> node = new Dictionary <string, object>(); node.Add(relationship.CreationDate, Conversion <DateTime, long> .Convert(trans.TransactionDate)); if (relationship.IsTimeDependent) { node.Add(relationship.StartDate, Conversion <DateTime, long> .Convert(startDate ?? DateTime.MinValue)); node.Add(relationship.EndDate, Conversion <DateTime, long> .Convert(endDate ?? DateTime.MaxValue)); } Dictionary <string, object?> parameters2 = new Dictionary <string, object?>(); parameters2.Add("inKey", inItem.GetKey()); parameters2.Add("outKey", outItem.GetKey()); parameters2.Add("node", node); string query = match + "\r\n" + create; relationship.RaiseOnRelationCreate(trans); trans.Run(query, parameters2); relationship.RaiseOnRelationCreated(trans); }
public override void Remove(Relationship relationship, OGM inItem, OGM outItem, DateTime?moment, bool timedependent) { Transaction trans = Transaction.RunningTransaction; Checks(relationship, inItem, outItem); string cypher; Dictionary <string, object?> parameters = new Dictionary <string, object?>(); parameters.Add("inKey", inItem.GetKey()); parameters.Add("outKey", outItem.GetKey()); if (timedependent) { parameters.Add("moment", Conversion <DateTime, long> .Convert(moment ?? DateTime.MinValue)); // End Current cypher = string.Format( "MATCH (in:{0})-[r:{1}]->(out:{2}) WHERE in.{3} = {{inKey}} and out.{4} = {{outKey}} and (r.{6} > {{moment}} OR r.{6} IS NULL) AND (r.{5} <={{moment}} OR r.{5} IS NULL) SET r.EndDate = {{moment}}", inItem.GetEntity().Label.Name, relationship.Neo4JRelationshipType, outItem.GetEntity().Label.Name, inItem.GetEntity().Key.Name, outItem.GetEntity().Key.Name, relationship.StartDate, relationship.EndDate); trans.Run(cypher, parameters); // Remove Future cypher = string.Format( "MATCH (in:{0})-[r:{1}]->(out:{2}) WHERE in.{3} = {{inKey}} and out.{4} = {{outKey}} and r.{5} > {{moment}} DELETE r", inItem.GetEntity().Label.Name, relationship.Neo4JRelationshipType, outItem.GetEntity().Label.Name, inItem.GetEntity().Key.Name, outItem.GetEntity().Key.Name, relationship.StartDate); relationship.RaiseOnRelationDelete(trans); RawResult result = trans.Run(cypher, parameters); relationship.RaiseOnRelationDeleted(trans); if (result.Statistics().RelationshipsDeleted == 0) { throw new ApplicationException($"Unable to delete time dependent future relationship '{relationship.Neo4JRelationshipType}' between {inItem.GetEntity().Label.Name}({inItem.GetKey()}) and {outItem.GetEntity().Label.Name}({outItem.GetKey()})"); } } else { cypher = string.Format( "MATCH (in:{0})-[r:{1}]->(out:{2}) WHERE in.{3} = {{inKey}} and out.{4} = {{outKey}} DELETE r", inItem.GetEntity().Label.Name, relationship.Neo4JRelationshipType, outItem.GetEntity().Label.Name, inItem.GetEntity().Key.Name, outItem.GetEntity().Key.Name); relationship.RaiseOnRelationDelete(trans); RawResult result = trans.Run(cypher, parameters); relationship.RaiseOnRelationDeleted(trans); if (result.Statistics().RelationshipsDeleted == 0) { throw new ApplicationException($"Unable to delete relationship '{relationship.Neo4JRelationshipType}' between {inItem.GetEntity().Label.Name}({inItem.GetKey()}) and {outItem.GetEntity().Label.Name}({outItem.GetKey()})"); } } }
protected virtual void Add(Transaction trans, Relationship relationship, OGM inItem, OGM outItem, DateTime moment) { // Expected behavior time dependent relationship: // ---------------------------------------------- // // Match existing relation where in & out item // IsAfter -> Remove relation // OverlapsOrIsAttached -> Set relation.EndDate to Conversion.MaxEndDate // // if (result.Statistics().PropertiesSet == 0) <--- this needs to be executed in the same statement // Add relation Entity inEntity = inItem.GetEntity(); Entity outEntity = outItem.GetEntity(); StringBuilder sb = new StringBuilder(); sb.AppendLine($"MATCH (in:{inEntity.Label.Name} {{ {inEntity.Key.Name}: $inKey }})-[rel:{relationship.Neo4JRelationshipType}]->(out:{outEntity.Label.Name} {{ {outEntity.Key.Name}: $outKey }})"); sb.AppendLine("WHERE COALESCE(rel.StartDate, $min) >= $moment"); sb.AppendLine("DELETE rel"); string delete = sb.ToString(); sb.Clear(); sb.AppendLine($"MATCH (in:{inEntity.Label.Name} {{ {inEntity.Key.Name}: $inKey }})-[rel:{relationship.Neo4JRelationshipType}]->(out:{outEntity.Label.Name} {{ {outEntity.Key.Name}: $outKey }})"); sb.AppendLine("WHERE COALESCE(rel.StartDate, $min) <= $moment AND COALESCE(rel.EndDate, $max) >= $moment"); sb.AppendLine("SET rel.EndDate = $max"); string update = sb.ToString(); sb.Clear(); sb.AppendLine($"MATCH (in:{inEntity.Label.Name} {{ {inEntity.Key.Name}: $inKey }}), (out:{outEntity.Label.Name} {{ {outEntity.Key.Name}: $outKey }})"); sb.AppendLine($"OPTIONAL MATCH (in)-[rel:{relationship.Neo4JRelationshipType}]->(out)"); sb.AppendLine("WHERE COALESCE(rel.StartDate, $min) <= $moment AND COALESCE(rel.EndDate, $max) >= $moment"); sb.AppendLine("WITH in, out, rel"); sb.AppendLine("WHERE rel is null"); sb.AppendLine($"CREATE (in)-[outr:{relationship.Neo4JRelationshipType}]->(out) SET outr.CreationDate = $create SET outr += $node"); string create = sb.ToString(); Dictionary <string, object> node = new Dictionary <string, object>(); node.Add(relationship.StartDate, Conversion <DateTime, long> .Convert(moment)); node.Add(relationship.EndDate, Conversion.MaxDateTimeInMS); Dictionary <string, object?> parameters = new Dictionary <string, object?>(); parameters.Add("inKey", inItem.GetKey()); parameters.Add("outKey", outItem.GetKey()); parameters.Add("create", Conversion <DateTime, long> .Convert(Transaction.RunningTransaction.TransactionDate)); parameters.Add("min", Conversion.MinDateTimeInMS); parameters.Add("max", Conversion.MaxDateTimeInMS); parameters.Add("moment", Conversion <DateTime, long> .Convert(moment)); parameters.Add("node", node); relationship.RaiseOnRelationCreate(trans); RawResult deleteResult = trans.Run(delete, parameters); RawResult updateResult = trans.Run(update, parameters); RawResult createResult = trans.Run(create, parameters); if (updateResult.Statistics().PropertiesSet > 0 && createResult.Statistics().RelationshipsCreated > 0) { throw new InvalidOperationException(); // TODO: Make the error better... } }