public CopyValidatorFromAttribute([NotNull] Type type, [NotNull] string propertyName, [NotNull, ItemNotNull] params Type[] ignoreAttributes)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }
            if (string.IsNullOrWhiteSpace(propertyName))
            {
                throw new ArgumentException("Value cannot be null or whitespace.", nameof(propertyName));
            }
            if (ignoreAttributes == null)
            {
                throw new ArgumentException("Is null.", nameof(ignoreAttributes));
            }
            if (ignoreAttributes.Any(a => a == null))
            {
                throw new ArgumentException("An element is null.", nameof(ignoreAttributes));
            }

            var property = type.GetProperty(propertyName) ??
                           throw new Exception($"A {nameof(CopyValidatorFromAttribute)} was created with Type={type.FullName} and PropertyName={propertyName}. The given type does not have a property with this name.");

            CopiedAttributes = property.GetCustomAttributes(true)
                               .SelectMany(attr =>
            {
                if (ignoreAttributes.Any(i => i.IsInstanceOfType(attr)))
                {
                    return(new KeyValuePair <Type, object> [0]);
                }

                AttributeTranslations.TryTranslateAttribute(attr, out var result);

                return(new [] { new KeyValuePair <Type, object>(result.GetType(), result) });
            })
                               .GroupBy(kvp => kvp.Key)
                               .ToDictionary(group => group.Key, group => group.Select(kvp => kvp.Value).ToArray());
        }
Пример #2
0
        private void CreateCheckExpressions(
            [NotNull] PropertyInfo property,
            [NotNull, ItemNotNull] ICollection <Expression> bodyExpressions,
            [NotNull] Expression modelParameter,
            [NotNull] Expression validatorParameter,
            [NotNull] Expression entityExpressionParameter,
            [NotNull] Expression hasNoValidationErrorsVariableExpression)
        {
            var delegateType = typeof(Func <,>).MakeGenericType(typeof(TEntity), property.PropertyType);

            var entityExpressionParameterVariable   = Expression.Variable(typeof(ParameterExpression), "entityExpressionParameter");
            var entityExpressionBodyVariable        = Expression.Variable(typeof(Expression), "entityExpressionBody");
            var propertyExpressionParameterVariable = Expression.Variable(typeof(ParameterExpression), "propertyExpressionParameter");
            var propertyExpressionBodyVariable      = Expression.Variable(typeof(Expression), "propertyExpressionBody");
            var propertyExpressionVariable          = Expression.Variable(typeof(Expression <>).MakeGenericType(delegateType), "propertyExpression");
            var propertyExpressionObjectVariable    = Expression.Variable(typeof(Expression <Func <TEntity, object> >), "propertyExpressionObject");

            // ReSharper disable once UseObjectOrCollectionInitializer
            var propertyBlockExpressions = new List <Expression>();

            propertyBlockExpressions.Add(Expression.Assign(entityExpressionParameterVariable,
                                                           Expression.Property(
                                                               Expression.Property(
                                                                   entityExpressionParameter,
                                                                   nameof(Expression <Func <TEntity, T> > .Parameters)),
                                                               typeof(ReadOnlyCollection <ParameterExpression>)
                                                               .GetDefaultMembers()
                                                               .OfType <PropertyInfo>()
                                                               .Single(p => p.GetIndexParameters().Length == 1 &&
                                                                       p.GetIndexParameters()[0].ParameterType == typeof(int)),
                                                               Expression.Constant(0, typeof(int)))));
            propertyBlockExpressions.Add(Expression.Assign(propertyExpressionParameterVariable, entityExpressionParameterVariable));
            propertyBlockExpressions.Add(Expression.Assign(entityExpressionBodyVariable,
                                                           Expression.Property(entityExpressionParameter, nameof(LambdaExpression.Body))));
            propertyBlockExpressions.Add(Expression.Assign(propertyExpressionBodyVariable,
                                                           Expression.Call(
                                                               typeof(Expression)
                                                               .GetMethods()
                                                               .Single(m => m.Name.Equals(nameof(Expression.Property)) &&
                                                                       m.GetParameters().Length == 2 &&
                                                                       m.GetParameters()[0].ParameterType == typeof(Expression) &&
                                                                       m.GetParameters()[1].ParameterType == typeof(PropertyInfo)),
                                                               entityExpressionBodyVariable,
                                                               Expression.Constant(property, typeof(PropertyInfo)))));
            propertyBlockExpressions.Add(Expression.Assign(propertyExpressionVariable,
                                                           Expression.Call(
                                                               typeof(Expression)
                                                               .GetMethods()
                                                               .Single(m => m.Name.Equals(nameof(Expression.Lambda)) &&
                                                                       m.IsGenericMethodDefinition &&
                                                                       m.GetParameters().Length == 2 &&
                                                                       m.GetParameters()[0].ParameterType == typeof(Expression) &&
                                                                       m.GetParameters()[1].ParameterType == typeof(ParameterExpression[]))
                                                               .MakeGenericMethod(delegateType),
                                                               propertyExpressionBodyVariable,
                                                               Expression.NewArrayInit(
                                                                   typeof(ParameterExpression),
                                                                   entityExpressionParameterVariable))));
            propertyBlockExpressions.Add(Expression.Assign(propertyExpressionObjectVariable,
                                                           Expression.Call(typeof(Expression)
                                                                           .GetMethods()
                                                                           .Single(m => m.Name.Equals(nameof(Expression.Lambda)) &&
                                                                                   m.IsGenericMethodDefinition &&
                                                                                   m.GetParameters().Length == 2 &&
                                                                                   m.GetParameters()[0].ParameterType == typeof(Expression) &&
                                                                                   m.GetParameters()[1].ParameterType == typeof(ParameterExpression[]))
                                                                           .MakeGenericMethod(typeof(Func <TEntity, object>)),
                                                                           Expression.Call(
                                                                               typeof(Expression)
                                                                               .GetMethods()
                                                                               .Single(m => m.Name.Equals(nameof(Expression.Convert)) &&
                                                                                       m.GetParameters().Length == 2 &&
                                                                                       m.GetParameters()[0].ParameterType == typeof(Expression) &&
                                                                                       m.GetParameters()[1].ParameterType == typeof(Type)),
                                                                               propertyExpressionBodyVariable,
                                                                               Expression.Constant(typeof(object), typeof(Type))),
                                                                           Expression.NewArrayInit(
                                                                               typeof(ParameterExpression),
                                                                               entityExpressionParameterVariable))));
            propertyBlockExpressions.Add(Expression.Assign(hasNoValidationErrorsVariableExpression, Expression.Constant(true)));


            var attributesByType = property.GetCustomAttributes(true)
                                   .GroupBy(a => a.GetType())
                                   .ToDictionary(group => group.Key, group => group.ToArray());

            if (attributesByType.TryGetValue(typeof(CopyValidatorFromAttribute), out var attributes))
            {
                foreach (var attribute in attributes)
                {
                    if (attribute is CopyValidatorFromAttribute copyValidatorFromAttribute)
                    {
                        foreach (var kvp in copyValidatorFromAttribute.CopiedAttributes)
                        {
                            var type = kvp.Key;
                            if (!attributesByType.TryGetValue(type, out var values))
                            {
                                values = new object[0];
                            }
                            attributesByType[type] = values.Union(kvp.Value).ToArray();
                        }
                    }
                }
            }

            attributesByType
            .SelectMany(kvp => kvp.Value)
            .Select(attr => AttributeTranslations.TryTranslateAttribute(attr, out var newAttr) ? newAttr : null)
            .Where(attr => attr != null)
            .ToList()
            .ForEach(attr =>
            {
                var type = attr.GetType();
                if (!attributesByType.TryGetValue(type, out var values))
                {
                    values = new object[0];
                }
                attributesByType[type] = values.Union(new[] { attr }).ToArray();
            });

            var nullableUnderlying = Nullable.GetUnderlyingType(property.PropertyType);
            var valueType          = nullableUnderlying ?? property.PropertyType;

            if (valueType.IsEnum)
            {
                propertyBlockExpressions.Add(
                    Expression.Assign(hasNoValidationErrorsVariableExpression,
                                      Expression.Call(
                                          validatorParameter,
                                          nameof(ValidatorBase.CheckEnumValueIsDefined),
                                          new[]
                {
                    typeof(TEntity),
                    property.PropertyType
                },
                                          Expression.Property(modelParameter, property),
                                          propertyExpressionVariable)));
            }

            if (attributesByType.TryGetValue(typeof(ValidatorCheckNotNullAttribute), out attributes))
            {
                foreach (var attribute in attributes)
                {
                    if (attribute is ValidatorCheckNotNullAttribute)
                    {
                        propertyBlockExpressions.Add(
                            Expression.Assign(hasNoValidationErrorsVariableExpression,
                                              Expression.Call(
                                                  validatorParameter,
                                                  nameof(ValidatorBase.CheckNotNull),
                                                  new[]
                        {
                            typeof(TEntity)
                        },
                                                  Expression.Property(modelParameter, property),
                                                  propertyExpressionObjectVariable)));
                    }
                }
            }

            if (attributesByType.TryGetValue(typeof(ValidatorCheckNotNullOrEmptyAttribute), out attributes))
            {
                foreach (var attribute in attributes)
                {
                    if (attribute is ValidatorCheckNotNullOrEmptyAttribute)
                    {
                        propertyBlockExpressions.Add(
                            Expression.Assign(hasNoValidationErrorsVariableExpression,
                                              Expression.Call(
                                                  validatorParameter,
                                                  nameof(ValidatorBase.CheckNotNullOrEmpty),
                                                  new[]
                        {
                            typeof(TEntity)
                        },
                                                  Expression.Property(modelParameter, property),
                                                  propertyExpressionVariable)));
                    }
                }
            }

            if (attributesByType.TryGetValue(typeof(ValidatorCheckNotEmptyAttribute), out attributes))
            {
                foreach (var attribute in attributes)
                {
                    if (attribute is ValidatorCheckNotEmptyAttribute)
                    {
                        propertyBlockExpressions.Add(
                            Expression.Assign(hasNoValidationErrorsVariableExpression,
                                              Expression.Call(
                                                  validatorParameter,
                                                  nameof(ValidatorBase.CheckNotEmpty),
                                                  new[]
                        {
                            typeof(TEntity)
                        },
                                                  Expression.Property(modelParameter, property),
                                                  propertyExpressionVariable)));
                    }
                }
            }

            if (attributesByType.TryGetValue(typeof(ValidatorCheckTrimmedAttribute), out attributes))
            {
                foreach (var attribute in attributes)
                {
                    if (attribute is ValidatorCheckTrimmedAttribute)
                    {
                        propertyBlockExpressions.Add(
                            Expression.Assign(hasNoValidationErrorsVariableExpression,
                                              Expression.Call(
                                                  validatorParameter,
                                                  nameof(ValidatorBase.CheckTrimmed),
                                                  new[]
                        {
                            typeof(TEntity)
                        },
                                                  Expression.Property(modelParameter, property),
                                                  propertyExpressionVariable)));
                    }
                }
            }

            if (attributesByType.TryGetValue(typeof(ValidatorCheckGreaterThanAttribute), out attributes))
            {
                foreach (var attribute in attributes)
                {
                    if (attribute is ValidatorCheckGreaterThanAttribute validatorCheckGreaterThanAttribute)
                    {
                        propertyBlockExpressions.Add(
                            Expression.Assign(hasNoValidationErrorsVariableExpression,
                                              Expression.Call(
                                                  validatorParameter,
                                                  nameof(ValidatorBase.CheckGreaterThan),
                                                  new[]
                        {
                            typeof(TEntity),
                            property.PropertyType
                        },
                                                  Expression.Constant(Convert.ChangeType(validatorCheckGreaterThanAttribute.Limit, property.PropertyType), property.PropertyType),
                                                  Expression.Property(modelParameter, property),
                                                  propertyExpressionVariable)));
                    }
                }
            }

            if (attributesByType.TryGetValue(typeof(ValidatorCheckGreaterThanEqualAttribute), out attributes))
            {
                foreach (var attribute in attributes)
                {
                    if (attribute is ValidatorCheckGreaterThanEqualAttribute validatorCheckGreaterThanEqualAttribute)
                    {
                        propertyBlockExpressions.Add(
                            Expression.Assign(hasNoValidationErrorsVariableExpression,
                                              Expression.Call(
                                                  validatorParameter,
                                                  nameof(ValidatorBase.CheckGreaterThanEqual),
                                                  new[]
                        {
                            typeof(TEntity),
                            property.PropertyType
                        },
                                                  Expression.Constant(Convert.ChangeType(validatorCheckGreaterThanEqualAttribute.Limit, property.PropertyType), property.PropertyType),
                                                  Expression.Property(modelParameter, property),
                                                  propertyExpressionVariable)));
                    }
                }
            }

            if (attributesByType.TryGetValue(typeof(ValidatorCheckLessThanAttribute), out attributes))
            {
                foreach (var attribute in attributes)
                {
                    if (attribute is ValidatorCheckLessThanAttribute validatorCheckLessThanAttribute)
                    {
                        propertyBlockExpressions.Add(
                            Expression.Assign(hasNoValidationErrorsVariableExpression,
                                              Expression.Call(
                                                  validatorParameter,
                                                  nameof(ValidatorBase.CheckLessThan),
                                                  new[]
                        {
                            typeof(TEntity),
                            property.PropertyType
                        },
                                                  Expression.Constant(Convert.ChangeType(validatorCheckLessThanAttribute.Limit, property.PropertyType), property.PropertyType),
                                                  Expression.Property(modelParameter, property),
                                                  propertyExpressionVariable)));
                    }
                }
            }

            if (attributesByType.TryGetValue(typeof(ValidatorCheckLessThanEqualAttribute), out attributes))
            {
                foreach (var attribute in attributes)
                {
                    if (attribute is ValidatorCheckLessThanEqualAttribute validatorCheckLessThanEqualAttribute)
                    {
                        propertyBlockExpressions.Add(
                            Expression.Assign(hasNoValidationErrorsVariableExpression,
                                              Expression.Call(
                                                  validatorParameter,
                                                  nameof(ValidatorBase.CheckLessThanEqual),
                                                  new[]
                        {
                            typeof(TEntity),
                            property.PropertyType
                        },
                                                  Expression.Constant(Convert.ChangeType(validatorCheckLessThanEqualAttribute.Limit, property.PropertyType), property.PropertyType),
                                                  Expression.Property(modelParameter, property),
                                                  propertyExpressionVariable)));
                    }
                }
            }

            if (attributesByType.TryGetValue(typeof(ValidatorCheckBetweenInclusiveAttribute), out attributes))
            {
                foreach (var attribute in attributes)
                {
                    if (attribute is ValidatorCheckBetweenInclusiveAttribute validatorCheckBetweenInclusiveAttribute)
                    {
                        propertyBlockExpressions.Add(
                            Expression.Assign(hasNoValidationErrorsVariableExpression,
                                              Expression.Call(
                                                  validatorParameter,
                                                  nameof(ValidatorBase.CheckBetweenInclusive),
                                                  new[]
                        {
                            typeof(TEntity),
                            property.PropertyType
                        },
                                                  Expression.Constant(Convert.ChangeType(validatorCheckBetweenInclusiveAttribute.LowerLimit, property.PropertyType), property.PropertyType),
                                                  Expression.Constant(Convert.ChangeType(validatorCheckBetweenInclusiveAttribute.UpperLimit, property.PropertyType), property.PropertyType),
                                                  Expression.Property(modelParameter, property),
                                                  propertyExpressionVariable)));
                    }
                }
            }

            if (attributesByType.TryGetValue(typeof(ValidatorCheckBetweenExclusiveAttribute), out attributes))
            {
                foreach (var attribute in attributes)
                {
                    if (attribute is ValidatorCheckBetweenExclusiveAttribute validatorCheckBetweenExclusiveAttribute)
                    {
                        propertyBlockExpressions.Add(
                            Expression.Assign(hasNoValidationErrorsVariableExpression,
                                              Expression.Call(
                                                  validatorParameter,
                                                  nameof(ValidatorBase.CheckBetweenExclusive),
                                                  new[]
                        {
                            typeof(TEntity),
                            property.PropertyType
                        },
                                                  Expression.Constant(Convert.ChangeType(validatorCheckBetweenExclusiveAttribute.LowerLimit, property.PropertyType), property.PropertyType),
                                                  Expression.Constant(Convert.ChangeType(validatorCheckBetweenExclusiveAttribute.UpperLimit, property.PropertyType), property.PropertyType),
                                                  Expression.Property(modelParameter, property),
                                                  propertyExpressionVariable)));
                    }
                }
            }

            if (attributesByType.TryGetValue(typeof(ValidatorCheckMaxStringLengthAttribute), out attributes))
            {
                foreach (var attribute in attributes)
                {
                    if (attribute is ValidatorCheckMaxStringLengthAttribute validatorCheckMaxStringLengthAttribute)
                    {
                        propertyBlockExpressions.Add(
                            Expression.Assign(hasNoValidationErrorsVariableExpression,
                                              Expression.Call(
                                                  validatorParameter,
                                                  nameof(ValidatorBase.CheckMaxStringLength),
                                                  new[]
                        {
                            typeof(TEntity)
                        },
                                                  Expression.Property(modelParameter, property),
                                                  Expression.Constant(validatorCheckMaxStringLengthAttribute.MaxLength, typeof(int)),
                                                  propertyExpressionVariable)));
                    }
                }
            }

            if (attributesByType.TryGetValue(typeof(ValidatorCheckItemNotNullAttribute), out attributes))
            {
                foreach (var attribute in attributes)
                {
                    if (attribute is ValidatorCheckItemNotNullAttribute)
                    {
                        propertyBlockExpressions.Add(
                            Expression.Assign(hasNoValidationErrorsVariableExpression,
                                              Expression.Call(
                                                  validatorParameter,
                                                  nameof(ValidatorBase.CheckItemNotNull),
                                                  new[]
                        {
                            typeof(TEntity),
                            property.PropertyType.GetElementType()
                        },
                                                  Expression.Property(modelParameter, property),
                                                  propertyExpressionVariable)));
                    }
                }
            }

            if (attributesByType.TryGetValue(typeof(ValidatorCheckDistinctAttribute), out attributes))
            {
                foreach (var attribute in attributes)
                {
                    if (attribute is ValidatorCheckDistinctAttribute validatorCheckDistinctAttribute)
                    {
                        if (validatorCheckDistinctAttribute.DistinctSelectEqualityMemberProviderType == null)
                        {
                            propertyBlockExpressions.Add(
                                Expression.Assign(hasNoValidationErrorsVariableExpression,
                                                  Expression.Call(
                                                      validatorParameter,
                                                      nameof(ValidatorBase.CheckDistinct),
                                                      new[]
                            {
                                typeof(TEntity),
                                property.PropertyType.GetElementType()
                            },
                                                      Expression.Property(modelParameter, property),
                                                      propertyExpressionVariable)));
                        }
                        else
                        {
                            var interfaceType = validatorCheckDistinctAttribute.DistinctSelectEqualityMemberProviderType
                                                .GetInterfaces()
                                                .SingleOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() ==
                                                                 typeof(IDistinctSelectEqualityMemberProvider <,>)) ?? throw new Exception(
                                                          $"{validatorCheckDistinctAttribute.DistinctSelectEqualityMemberProviderType.FullName} is not of type {typeof(IDistinctSelectEqualityMemberProvider<,>).FullName}.");

                            var equalityMemberType = interfaceType.GetGenericArguments()[1];

                            propertyBlockExpressions.Add(
                                Expression.Assign(hasNoValidationErrorsVariableExpression,
                                                  Expression.Call(
                                                      validatorParameter,
                                                      nameof(ValidatorBase.CheckDistinct),
                                                      new[]
                            {
                                typeof(TEntity),
                                property.PropertyType.GetElementType(),
                                equalityMemberType
                            },
                                                      Expression.Property(modelParameter, property),
                                                      Expression.Constant(Activator.CreateInstance(validatorCheckDistinctAttribute.DistinctSelectEqualityMemberProviderType), validatorCheckDistinctAttribute.DistinctSelectEqualityMemberProviderType),
                                                      propertyExpressionVariable)));
                        }
                    }
                }
            }

            if (attributesByType.TryGetValue(typeof(ValidatorCheckRecursiveAttribute), out attributes))
            {
                foreach (var attribute in attributes)
                {
                    if (attribute is ValidatorCheckRecursiveAttribute)
                    {
                        if (property.PropertyType.IsArray)
                        {
                            var elementType = property.PropertyType.GetElementType() ?? throw new Exception($"{property.Name} is of type {property.PropertyType.FullName} and has a {typeof(ValidatorCheckRecursiveAttribute).FullName} defined. This attribute is only supported for array types.");

                            var validator = ModelValidators.GetValidator(typeof(TEntity), elementType);

                            var arrayVariableExpression = Expression.Variable(property.PropertyType, "array");
                            var idxVariableExpression   = Expression.Variable(typeof(int), "idx");
                            var itemVariableExpression  = Expression.Variable(elementType, "item");

                            var loopBreakLabel = Expression.Label();


                            // array = <model>.<property>;
                            var assignArrayVariable = Expression.Assign(arrayVariableExpression, Expression.Property(modelParameter, property));

                            // array != null
                            var arrayVariableIsNotNull = Expression.NotEqual(arrayVariableExpression, Expression.Constant(null, property.PropertyType));

                            // hasNoValidationErrors == false
                            var hasValidationErrorsVariableExpressionIsFalse = Expression.Equal(hasNoValidationErrorsVariableExpression, Expression.Constant(true));

                            // idx = 0;
                            var setIdxVariable0 = Expression.Assign(idxVariableExpression, Expression.Constant(0, typeof(int)));

                            // array.Length
                            var arrayLength = Expression.Property(arrayVariableExpression, nameof(Array.Length));

                            // idx < array.Length
                            var idxLessThanArrayLength = Expression.LessThan(idxVariableExpression, arrayLength);

                            // item = array[idx];
                            var assignItemVariable = Expression.Assign(itemVariableExpression, Expression.ArrayIndex(arrayVariableExpression, idxVariableExpression));

                            // item != null
                            var itemVariableIsNull = Expression.NotEqual(itemVariableExpression, Expression.Constant(null, elementType));

                            // <validator instance>
                            var modelValidatorConstant = Expression.Constant(validator, typeof(IModelValidator));

                            // MethodInfo of IModelValidator.Validate(object, ValidatorBase, LambdaExpression)
                            var methodInfoValidatorValidateMethod = typeof(IModelValidator)
                                                                    .GetMethods()
                                                                    .SingleOrDefault(m =>
                                                                                     m.Name.Equals(nameof(IModelValidator.Validate)) &&
                                                                                     m.GetParameters().Length == 3) ??
                                                                    throw new Exception(
                                                                              $"Could not find method {nameof(IModelValidator)}.{nameof(IModelValidator.Validate)}");

                            // MethodInfo of Expression.Lambda<Func<TEntity, <elementType>>>(Expression, ParameterExpression[])
                            var methodInfoExpressionLambdaEntityTypeToElementType = typeof(Expression)
                                                                                    .GetMethods()
                                                                                    .Single(m => m.Name.Equals(nameof(Expression.Lambda)) &&
                                                                                            m.IsGenericMethodDefinition &&
                                                                                            m.GetParameters().Length == 2 &&
                                                                                            m.GetParameters()[0].ParameterType == typeof(Expression) &&
                                                                                            m.GetParameters()[1].ParameterType == typeof(ParameterExpression[]))
                                                                                    .MakeGenericMethod(typeof(Func <,>).MakeGenericType(typeof(TEntity), elementType));

                            // MethodInfo of Expression.ArrayIndex(Expression, Expression)
                            var methodInfoExpressionArrayIndex = typeof(Expression)
                                                                 .GetMethods()
                                                                 .Single(m =>
                                                                         m.Name.Equals(nameof(Expression.ArrayIndex)) &&
                                                                         m.GetParameters().Length == 2 &&
                                                                         m.GetParameters()[0].ParameterType == typeof(Expression) &&
                                                                         m.GetParameters()[1].ParameterType == typeof(Expression));

                            // MethodInfo of Expression.Constant(object, Type)
                            var methodInfoExpressionConstant = typeof(Expression)
                                                               .GetMethods()
                                                               .Single(m =>
                                                                       m.Name.Equals(nameof(Expression.Constant)) &&
                                                                       m.GetParameters().Length == 2 &&
                                                                       m.GetParameters()[0].ParameterType == typeof(object) &&
                                                                       m.GetParameters()[1].ParameterType == typeof(Type));

                            // Expression.Constant((object)idx, typeof(int))
                            var idxVariableAsConstant = Expression.Call(
                                methodInfoExpressionConstant,
                                Expression.Convert(idxVariableExpression, typeof(object)),
                                Expression.Constant(typeof(int), typeof(Type)));

                            // Expression.ArrayIndex(<propertyExpression>.Body, Expression.Constant((object)idx, typeof(int)))
                            var arrayItemExpression = Expression.Call(methodInfoExpressionArrayIndex,
                                                                      propertyExpressionBodyVariable,
                                                                      idxVariableAsConstant);

                            // Expression.Lambda<Func<TEntity, <elementType>>>(<arrayItemExpression>, new ParameterExpression[] { <propertyExpression>.Parameters[0] })
                            var makeIndexedPropertyExpression = Expression.Call(
                                methodInfoExpressionLambdaEntityTypeToElementType,
                                arrayItemExpression,
                                Expression.NewArrayInit(
                                    typeof(ParameterExpression),
                                    propertyExpressionParameterVariable));


                            // <modelValidatorConstant>.Validate(item, validator, <makeIndexedPropertyExpression>);
                            var callModelValidator = Expression.Call(
                                modelValidatorConstant,
                                methodInfoValidatorValidateMethod,
                                itemVariableExpression,
                                validatorParameter,
                                makeIndexedPropertyExpression
                                );

                            // <elementType> item;
                            // item = array[idx];
                            // if (item != null)
                            // {
                            //    <callModelValidator>
                            // }
                            // ++ idx;
                            var visitItemBlock = Expression.Block(
                                new[]
                            {
                                itemVariableExpression
                            },
                                assignItemVariable,
                                Expression.IfThen(itemVariableIsNull, callModelValidator),
                                Expression.PreIncrementAssign(idxVariableExpression)
                                );

                            // while(true)
                            // {
                            //    if (idx < array.length)
                            //    {
                            //       <visitItemBlock>
                            //    }
                            //    break;
                            // }
                            var loopOverItems = Expression.Loop(
                                Expression.IfThenElse(
                                    idxLessThanArrayLength,
                                    visitItemBlock,
                                    Expression.Break(loopBreakLabel)),
                                loopBreakLabel);

                            // <elementType>[] array;
                            // array = <model>.<property>;
                            // if (hasValidationErrors == false && array != null)
                            // {
                            //    int idx;
                            //    idx = 0;
                            //    <loopOverItems>
                            // }
                            var propertyBlock = Expression.Block(
                                new[]
                            {
                                arrayVariableExpression
                            },
                                assignArrayVariable,
                                Expression.IfThen(
                                    Expression.AndAlso(
                                        hasValidationErrorsVariableExpressionIsFalse,
                                        arrayVariableIsNotNull),
                                    Expression.Block(
                                        new[]
                            {
                                idxVariableExpression
                            },
                                        setIdxVariable0,
                                        loopOverItems)));

                            propertyBlockExpressions.Add(propertyBlock);
                        }
                        else
                        {
                            var validator = ModelValidators.GetValidator(typeof(TEntity), property.PropertyType);

                            propertyBlockExpressions.Add(
                                Expression.IfThen(
                                    Expression.AndAlso(
                                        Expression.Equal(hasNoValidationErrorsVariableExpression, Expression.Constant(true)),
                                        Expression.NotEqual(Expression.Property(modelParameter, property), Expression.Constant(null, property.PropertyType))),
                                    Expression.Call(
                                        Expression.Constant(validator, typeof(IModelValidator)),
                                        typeof(IModelValidator)
                                        .GetMethods()
                                        .SingleOrDefault(m => m.Name.Equals(nameof(IModelValidator.Validate)) &&
                                                         m.GetParameters().Length == 3) ?? throw new Exception($"Could not find method {nameof(IModelValidator)}.{nameof(IModelValidator.Validate)}"),
                                        Expression.Property(modelParameter, property),
                                        validatorParameter,
                                        propertyExpressionVariable)));
                        }
                    }
                }
            }

            bodyExpressions.Add(
                Expression.Block(
                    new []
            {
                entityExpressionParameterVariable,
                entityExpressionBodyVariable,
                propertyExpressionParameterVariable,
                propertyExpressionBodyVariable,
                propertyExpressionVariable,
                propertyExpressionObjectVariable
            },
                    propertyBlockExpressions.ToArray()
                    ));
        }