<commit_before>using Signum.Engine.Basics;
using Signum.Engine.Maps;
using Signum.Engine.Operations;
using Signum.Entities;
using Signum.Entities.Reflection;
using Signum.Utilities;
using Signum.Utilities.DataStructures;
using Signum.Utilities.Reflection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Signum.Engine
{
    public static class VirtualMList
    {
        //Order, OrderLine, Order.Lines
        public static Dictionary<Type, Dictionary<Type, VirtualMListInfo>> RegisteredVirtualMLists = new Dictionary<Type, Dictionary<Type, VirtualMListInfo>>();

        static readonly Variable<ImmutableStack<Type>> avoidTypes = Statics.ThreadVariable<ImmutableStack<Type>>("avoidVirtualMList");

        public static bool ShouldAvoidMListType(Type elementType)
        {
            var stack = avoidTypes.Value;
            return (stack != null && (stack.Contains(elementType) || stack.Contains(typeof(object))));
        }

        public static bool IsVirtualMList(this PropertyRoute pr)
        {
            return pr.Type.IsMList() && (RegisteredVirtualMLists.TryGetC(pr.RootType)?.TryGetC(pr.Type.ElementType()!)?.Equals(pr) ?? false);
        }

        /// <param name="elementType">Use null for every type</param>
        public static IDisposable AvoidMListType(Type elementType)
        {
            avoidTypes.Value = (avoidTypes.Value ?? ImmutableStack<Type>.Empty).Push(elementType);

            return new Disposable(() => avoidTypes.Value = avoidTypes.Value.Pop());
        }

        static readonly Variable<ImmutableStack<Type>> considerNewTypes = Statics.ThreadVariable<ImmutableStack<Type>>("considerNewTypes");

        public static bool ShouldConsiderNew(Type parentType)
        {
            var stack = considerNewTypes.Value;
            return (stack != null && (stack.Contains(parentType) || stack.Contains(typeof(object))));
        }

        /// <param name="parentType">Use null for every type</param>
        public static IDisposable ConsiderNewType(Type parentType)
        {
            considerNewTypes.Value = (considerNewTypes.Value ?? ImmutableStack<Type>.Empty).Push(parentType);

            return new Disposable(() => considerNewTypes.Value = considerNewTypes.Value.Pop());
        }

        public static FluentInclude<T> WithVirtualMList<T, L>(this FluentInclude<T> fi,
            Expression<Func<T, MList<L>>> mListField,
            Expression<Func<L, Lite<T>?>> backReference,
            ExecuteSymbol<L> saveOperation,
            DeleteSymbol<L> deleteOperation)
            where T : Entity
            where L : Entity
        {

            return fi.WithVirtualMList(mListField, backReference,
                onSave: saveOperation == null ? null : new Action<L, T>((line, e) =>
                {
                    line.Execute(saveOperation);
                }),
                onRemove: deleteOperation == null ? null : new Action<L, T>((line, e) =>
                {
                    line.Delete(deleteOperation);
                }));
        }

        public static FluentInclude<T> WithVirtualMList<T, L>(this FluentInclude<T> fi,
            Expression<Func<T, MList<L>>> mListField,
            Expression<Func<L, Lite<T>?>> backReference,
            Action<L, T>? onSave = null,
            Action<L, T>? onRemove = null,
            bool? lazyRetrieve = null,
            bool? lazyDelete = null) //To avoid StackOverflows
            where T : Entity
            where L : Entity
        {
            fi.SchemaBuilder.Include<L>();

            var mListPropertRoute = PropertyRoute.Construct(mListField);
            var backReferenceRoute = PropertyRoute.Construct(backReference, avoidLastCasting: true);
            if (fi.SchemaBuilder.Settings.FieldAttribute<IgnoreAttribute>(mListPropertRoute) == null)
                throw new InvalidOperationException($"The property {mListPropertRoute} should have an IgnoreAttribute to be used as Virtual MList");

            RegisteredVirtualMLists.GetOrCreate(typeof(T)).Add(typeof(L), new VirtualMListInfo(mListPropertRoute, backReferenceRoute));

            var defLazyRetrieve = lazyRetrieve ?? (typeof(L) == typeof(T));
            var defLazyDelete = lazyDelete ?? (typeof(L) == typeof(T));

            Func<T, MList<L>> getMList = GetAccessor(mListField);
            Action<L, Lite<T>>? setter = null;
            bool preserveOrder = fi.SchemaBuilder.Settings.FieldAttributes(mListPropertRoute)
                .OfType<PreserveOrderAttribute>()
                .Any();

            if (preserveOrder && !typeof(ICanBeOrdered).IsAssignableFrom(typeof(L)))
                throw new InvalidOperationException($"'{typeof(L).Name}' should implement '{nameof(ICanBeOrdered)}' because '{ReflectionTools.GetPropertyInfo(mListField).Name}' contains '[{nameof(PreserveOrderAttribute)}]'");

            var sb = fi.SchemaBuilder;

            if (defLazyRetrieve)
            {
                sb.Schema.EntityEvents<T>().Retrieved += (T e, PostRetrievingContext ctx) =>
                {
                    if (ShouldAvoidMListType(typeof(L)))
                        return;

                    var mlist = getMList(e);

                    if (mlist == null)
                        return;

                    var query = Database.Query<L>()
                        .Where(line => backReference.Evaluate(line) == e.ToLite());<commit_msg>simplify RegisteredVirtualMLists<commit_after>using Signum.Engine.Basics;
using Signum.Engine.Linq;
using Signum.Engine.Maps;
using Signum.Engine.Operations;
using Signum.Entities;
using Signum.Entities.Reflection;
using Signum.Utilities;
using Signum.Utilities.DataStructures;
using Signum.Utilities.Reflection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Signum.Engine
{
    public static class VirtualMList
    {
        //Order, OrderLine, Order.Lines
        public static Dictionary<Type, Dictionary<PropertyRoute, VirtualMListInfo>> RegisteredVirtualMLists = new Dictionary<Type, Dictionary<PropertyRoute, VirtualMListInfo>>();

        static readonly Variable<ImmutableStack<Type>> avoidTypes = Statics.ThreadVariable<ImmutableStack<Type>>("avoidVirtualMList");

        public static bool ShouldAvoidMListType(Type elementType)
        {
            var stack = avoidTypes.Value;
            return (stack != null && (stack.Contains(elementType) || stack.Contains(typeof(object))));
        }

        public static bool IsVirtualMList(this PropertyRoute pr)
        {
            return pr.Type.IsMList() && (RegisteredVirtualMLists.TryGetC(pr.RootType)?.ContainsKey(pr) ?? false);
        }

        /// <param name="elementType">Use null for every type</param>
        public static IDisposable AvoidMListType(Type elementType)
        {
            avoidTypes.Value = (avoidTypes.Value ?? ImmutableStack<Type>.Empty).Push(elementType);

            return new Disposable(() => avoidTypes.Value = avoidTypes.Value.Pop());
        }

        static readonly Variable<ImmutableStack<Type>> considerNewTypes = Statics.ThreadVariable<ImmutableStack<Type>>("considerNewTypes");

        public static bool ShouldConsiderNew(Type parentType)
        {
            var stack = considerNewTypes.Value;
            return (stack != null && (stack.Contains(parentType) || stack.Contains(typeof(object))));
        }

        /// <param name="parentType">Use null for every type</param>
        public static IDisposable ConsiderNewType(Type parentType)
        {
            considerNewTypes.Value = (considerNewTypes.Value ?? ImmutableStack<Type>.Empty).Push(parentType);

            return new Disposable(() => considerNewTypes.Value = considerNewTypes.Value.Pop());
        }

        public static FluentInclude<T> WithVirtualMList<T, L>(this FluentInclude<T> fi,
            Expression<Func<T, MList<L>>> mListField,
            Expression<Func<L, Lite<T>?>> backReference,
            ExecuteSymbol<L> saveOperation,
            DeleteSymbol<L> deleteOperation)
            where T : Entity
            where L : Entity
        {

            return fi.WithVirtualMList(mListField, backReference,
                onSave: saveOperation == null ? null : new Action<L, T>((line, e) =>
                {
                    line.Execute(saveOperation);
                }),
                onRemove: deleteOperation == null ? null : new Action<L, T>((line, e) =>
                {
                    line.Delete(deleteOperation);
                }));
        }

        public static FluentInclude<T> WithVirtualMList<T, L>(this FluentInclude<T> fi,
            Expression<Func<T, MList<L>>> mListField,
            Expression<Func<L, Lite<T>?>> backReference,
            Action<L, T>? onSave = null,
            Action<L, T>? onRemove = null,
            bool? lazyRetrieve = null,
            bool? lazyDelete = null) //To avoid StackOverflows
            where T : Entity
            where L : Entity
        {
            fi.SchemaBuilder.Include<L>();

            var mListPropertRoute = PropertyRoute.Construct(mListField);
            var backReferenceRoute = PropertyRoute.Construct(backReference, avoidLastCasting: true);
            if (fi.SchemaBuilder.Settings.FieldAttribute<IgnoreAttribute>(mListPropertRoute) == null)
                throw new InvalidOperationException($"The property {mListPropertRoute} should have an IgnoreAttribute to be used as Virtual MList");

            RegisteredVirtualMLists.GetOrCreate(typeof(T)).Add(mListPropertRoute, new VirtualMListInfo(mListPropertRoute, backReferenceRoute));

            var defLazyRetrieve = lazyRetrieve ?? (typeof(L) == typeof(T));
            var defLazyDelete = lazyDelete ?? (typeof(L) == typeof(T));

            Func<T, MList<L>> getMList = GetAccessor(mListField);
            Action<L, Lite<T>>? setter = null;
            bool preserveOrder = fi.SchemaBuilder.Settings.FieldAttributes(mListPropertRoute)
                .OfType<PreserveOrderAttribute>()
                .Any();

            if (preserveOrder && !typeof(ICanBeOrdered).IsAssignableFrom(typeof(L)))
                throw new InvalidOperationException($"'{typeof(L).Name}' should implement '{nameof(ICanBeOrdered)}' because '{ReflectionTools.GetPropertyInfo(mListField).Name}' contains '[{nameof(PreserveOrderAttribute)}]'");

            var sb = fi.SchemaBuilder;

            if (defLazyRetrieve)
            {
                sb.Schema.EntityEvents<T>().Retrieved += (T e, PostRetrievingContext ctx) =>
                {
                    if (ShouldAvoidMListType(typeof(L)))
                        return;

                    var mlist = getMList(e);

                    if (mlist == null)
                        return;

                    var query = Database.Query<L>()
                        .Where(line => backReference.Evaluate(line) == e.ToLite());
{'fraction_non_alphanumeric': 0.10963667978402124, 'fraction_numerical': 0.0, 'mean_word_length': 2.519484702093398, 'pattern_counts': {'":': 0, '<': 105, '<?xml version=': 0, '>': 117, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3853799', 'n_tokens_mistral': 2946, 'n_tokens_neox': 2843, 'n_words': 602}