Exemple #1
0
        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);
        }
Exemple #4
0
        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);
            }
        }
Exemple #5
0
        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;
        }
Exemple #6
0
        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()})");
        }
Exemple #10
0
        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);
        }
Exemple #12
0
        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...
            }
        }