private static IEnumerable <ReferingType> GetReferingTypes(IModel model, Type entityType)
        {
            if (ReferingTypesListsCache.TryGetValue(entityType, out List <ReferingType> referingTypes))
            {
                return(referingTypes);
            }

            referingTypes = new List <ReferingType>();
            if (!ReferingTypesListsCache.TryAdd(entityType, referingTypes))
            {
                //it is there already (done in another thread)
                return(ReferingTypesListsCache[entityType]);
            }

            //find all potential references
            var types = model.Metadata.Types().Where(t => typeof(IInstantiableEntity).GetTypeInfo().IsAssignableFrom(t.Type));

            // ReSharper disable once LoopCanBeConvertedToQuery
            foreach (var type in types)
            {
                if (!ReferingTypesCache.TryGetValue(type.Type, out ReferingType rt))
                {
                    var singleReferences = type.Properties.Values.Where(p =>
                                                                        p.EntityAttribute != null && p.EntityAttribute.Order > 0 &&
                                                                        p.PropertyInfo.PropertyType.GetTypeInfo().IsAssignableFrom(entityType)).ToList();
                    var listReferences = type.Properties.Values.Where(p =>
                                                                      p.EntityAttribute != null && p.EntityAttribute.Order > 0 &&
                                                                      p.PropertyInfo.PropertyType.GetTypeInfo().IsGenericType&&
                                                                      p.PropertyInfo.PropertyType.GenericTypeArgumentIsAssignableFrom(entityType)).ToList();
                    var nestedListReferences = type.Properties.Values.Where(p =>
                                                                            p.EntityAttribute != null && p.EntityAttribute.Order > 0 &&
                                                                            p.PropertyInfo.PropertyType.GetTypeInfo().IsGenericType&&
                                                                            p.PropertyInfo.PropertyType.GetItemTypeFromGenericType().IsGenericType&&
                                                                            p.PropertyInfo.PropertyType.GetItemTypeFromGenericType().GenericTypeArgumentIsAssignableFrom(entityType)).ToList();
                    if (!singleReferences.Any() && !listReferences.Any() && !nestedListReferences.Any())
                    {
                        continue;
                    }

                    rt = new ReferingType {
                        Type = type, SingleReferences = singleReferences, ListReferences = listReferences, NestedListReferences = nestedListReferences
                    };
                    ReferingTypesCache.TryAdd(type.Type, rt);
                }
                referingTypes.Add(rt);
            }
            return(referingTypes);
        }
        /// <summary>
        /// Deletes references to specified entity from all entities in the model where entity is
        /// a references as an object or as a member of a collection.
        /// </summary>
        /// <param name="model">Model to be used</param>
        /// <param name="entities">Entities to be replaced</param>
        /// <param name="referingType">Candidate type containing reference to the type of entity</param>
        /// <param name="replacement">New reference. If this is null it just removes references to entity</param>
        private static void ReplaceReferences <TEntity, TReplacement>(IModel model, IEnumerable <TEntity> entities, ReferingType referingType, TReplacement replacement)
            where TEntity : IPersistEntity where TReplacement : TEntity
        {
            if (entities == null || !entities.Any())
            {
                return;
            }

            // use hash set for quick object reference matching
            var hash = new HashSet <object>(entities.Cast <object>());

            //get all instances of this type and nullify and remove the entity
            var entitiesToCheck = model.Instances.OfType(referingType.Type.Type.Name, true);

            foreach (var toCheck in entitiesToCheck)
            {
                //check properties
                foreach (var pInfo in referingType.SingleReferences.Select(p => p.PropertyInfo))
                {
                    var pVal = pInfo.GetValue(toCheck);
                    if (pVal == null && replacement == null)
                    {
                        continue;
                    }

                    //it is enough to compare references
                    if (!hash.Contains(pVal))
                    {
                        continue;
                    }
                    pInfo.SetValue(toCheck, replacement);
                }

                foreach (var pInfo in referingType.ListReferences.Select(p => p.PropertyInfo))
                {
                    var pVal = pInfo.GetValue(toCheck);
                    if (pVal == null)
                    {
                        continue;
                    }

                    //it might be uninitialized optional item set
                    if (pVal is IOptionalItemSet optSet && !optSet.Initialized)
                    {
                        continue;
                    }

                    //or it is non-optional item set implementing IList
                    if (!(pVal is IList itemSet))
                    {
                        throw new XbimException($"Unable to remove items from {referingType.Type.Name}.{pInfo.Name}. No IList implementation.");
                    }

                    for (int i = 0; i < itemSet.Count; i++)
                    {
                        var item = itemSet[i];
                        if (!hash.Contains(item))
                        {
                            continue;
                        }
                        itemSet.RemoveAt(i);
                        if (replacement != null)
                        {
                            itemSet.Insert(i, replacement);
                        }
                        else
                        {
                            i--; // keep in sync
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Deletes references to specified entity from all entities in the model where entity is
        /// a references as an object or as a member of a collection.
        /// </summary>
        /// <param name="model">Model to be used</param>
        /// <param name="entity">Entity to be removed from references</param>
        /// <param name="referingType">Candidate type containing reference to the type of entity</param>
        /// <param name="replacement">New reference. If this is null it just removes references to entity</param>
        private static void ReplaceReferences <TEntity, TReplacement>(IModel model, TEntity entity, ReferingType referingType, TReplacement replacement)
            where TEntity : IPersistEntity where TReplacement : TEntity
        {
            if (entity == null)
            {
                return;
            }

            //get all instances of this type and nullify and remove the entity
            var entitiesToCheck = model.Instances.OfType(referingType.Type.Type.Name, true);

            foreach (var toCheck in entitiesToCheck)
            {
                //check properties
                foreach (var pInfo in referingType.SingleReferences.Select(p => p.PropertyInfo))
                {
                    var pVal = pInfo.GetValue(toCheck);
                    if (pVal == null && replacement == null)
                    {
                        continue;
                    }

                    //it is enough to compare references
                    if (!ReferenceEquals(pVal, entity))
                    {
                        continue;
                    }
                    pInfo.SetValue(toCheck, replacement);
                }

                foreach (var pInfo in referingType.ListReferences.Select(p => p.PropertyInfo))
                {
                    var pVal = pInfo.GetValue(toCheck);
                    if (pVal == null)
                    {
                        continue;
                    }

                    //it might be uninitialized optional item set
                    var optSet = pVal as IOptionalItemSet;
                    if (optSet != null && !optSet.Initialized)
                    {
                        continue;
                    }

                    //or it is non-optional item set implementing IList
                    var itemSet = pVal as IList;
                    if (itemSet != null)
                    {
                        if (itemSet.Contains(entity))
                        {
                            itemSet.Remove(entity);
                        }
                        if (replacement != null)
                        {
                            itemSet.Add(replacement);
                        }
                        continue;
                    }

                    //fall back operating on common list functions using reflection (this is slow)
                    var contMethod = pInfo.PropertyType.GetTypeInfo().GetMethod("Contains");
                    if (contMethod == null)
                    {
                        var msg =
                            string.Format(
                                "It wasn't possible to check containment of entity {0} in property {1} of {2}. No suitable method found.",
                                entity.GetType().Name, pInfo.Name, toCheck.GetType().Name);
                        throw new XbimException(msg);
                    }
                    var contains = (bool)contMethod.Invoke(pVal, new object[] { entity });
                    if (!contains)
                    {
                        continue;
                    }
                    var removeMethod = pInfo.PropertyType.GetTypeInfo().GetMethod("Remove");
                    if (removeMethod == null)
                    {
                        var msg =
                            string.Format(
                                "It wasn't possible to remove reference to entity {0} in property {1} of {2}. No suitable method found.",
                                entity.GetType().Name, pInfo.Name, toCheck.GetType().Name);
                        throw new XbimException(msg);
                    }
                    removeMethod.Invoke(pVal, new object[] { entity });

                    if (replacement == null)
                    {
                        continue;
                    }

                    var addMethod = pInfo.PropertyType.GetTypeInfo().GetMethod("Add");
                    if (addMethod == null)
                    {
                        var msg =
                            string.Format(
                                "It wasn't possible to add reference to entity {0} in property {1} of {2}. No suitable method found.",
                                entity.GetType().Name, pInfo.Name, toCheck.GetType().Name);
                        throw new XbimException(msg);
                    }
                    addMethod.Invoke(pVal, new object[] { replacement });
                }
            }
        }