protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
        {
            directlyInSelectMethod = false;

            var rightSide = node.Expression as MemberExpression;

            if (rightSide is null && node.Expression != null && node.Expression.NodeType == ExpressionType.Convert)
            {
                rightSide = ((UnaryExpression)node.Expression).Operand as MemberExpression;
            }

            var property = rightSide?.Member as PropertyInfo;

            if (property != null)
            {
                PropertyLocalizationMapping mapping = GetLocalizationMapping(property);
                if (mapping != null)
                {
                    var localizationExpresison = GetLocalizationExpression(rightSide, mapping, _culture);
                    if (localizationExpresison != null)
                    {
                        node = Expression.Bind(node.Member, localizationExpresison);
                    }
                }

                return(base.VisitMemberAssignment(node));
            }

            return(base.VisitMemberAssignment(node));
        }
        protected override Expression VisitNew(NewExpression node)
        {
            directlyInSelectMethod = false;

            return(Expression.New(node.Constructor, node.Arguments.Select(arg =>
            {
                var rightSide = arg as MemberExpression;
                var property = rightSide?.Member as PropertyInfo;

                if (property != null)
                {
                    PropertyLocalizationMapping mapping = GetLocalizationMapping(property);
                    if (mapping != null)
                    {
                        var localizationExpresison = GetLocalizationExpression(rightSide, mapping, _culture);
                        if (localizationExpresison != null)
                        {
                            return localizationExpresison;
                        }
                    }
                }

                return Visit(arg);
            })));
        }
        private PropertyLocalizationMapping GetLocalizationMapping(PropertyInfo sourceProperty)
        {
            if (sourceProperty is null)
            {
                return(null);
            }
            else if (sourceProperty.PropertyType != typeof(string))
            {
                return(null);
            }

            var replaceNodeAttribute = sourceProperty
                                       .GetCustomAttributes(typeof(LocalizedQueryPlaceHolderAttribute), false)
                                       .Cast <LocalizedQueryPlaceHolderAttribute>()
                                       .FirstOrDefault();

            if (replaceNodeAttribute is null)
            {
                return(null);
            }

            return(PropertyLocalizationMappings.GetOrAdd(sourceProperty, _ =>
            {
                if (sourceProperty.ReflectedType is null || sourceProperty.ReflectedType != typeof(LocalizedValueObject))
                {
                    return null;
                }

                var mapping = new PropertyLocalizationMapping
                {
                    ValueObjectType = sourceProperty.ReflectedType,
                };

                mapping.SerializedPropertyInfo = mapping.ValueObjectType.GetProperties().FirstOrDefault(p =>
                                                                                                        p.PropertyType == typeof(string) && p.Name == nameof(LocalizedValueObject.Serialized));

                if (mapping.SerializedPropertyInfo is null)
                {
                    return null;
                }

                return mapping;
            }));
        protected override Expression VisitMember(MemberExpression node)
        {
            var property = node.Member as PropertyInfo;

            if (directlyInSelectMethod)
            {
                PropertyLocalizationMapping mapping = GetLocalizationMapping(property);
                if (mapping != null)
                {
                    var localizationExpresison = GetLocalizationExpression(node, mapping, _culture, property.PropertyType);
                    if (localizationExpresison != null)
                    {
                        return(localizationExpresison);
                    }
                }

                return(base.VisitMember(node));
            }

            return(base.VisitMember(node));
        }
        private static Expression GetLocalizationExpression(MemberExpression memberExpression, PropertyLocalizationMapping mapping,
                                                            string culture, Type customReturnType = null)
        {
            // this method transforms member expression { e.Property.Localized }
            // to { EntityFrameworkJsonExtensions.Value(e.Property.Serialized, "$.culture") ?? EntityFrameworkJsonExtensions.Value(e.Property.Serialized, "$.default") }

            var baseLocalizationProperty = memberExpression.Expression;                                                         // {e.Property}

            var serializedLocalizationProperty = Expression.Property(baseLocalizationProperty, mapping.SerializedPropertyInfo); // {e.Property.Serialized}

            var localizedExpression = Expression.Call(typeof(EntityFrameworkJsonExtensions), nameof(EntityFrameworkJsonExtensions.Value)
                                                      , null, serializedLocalizationProperty, Expression.Constant($"$.{culture}"));
            var defaultExpression = Expression.Call(typeof(EntityFrameworkJsonExtensions), nameof(EntityFrameworkJsonExtensions.Value)
                                                    , null, serializedLocalizationProperty, Expression.Constant($"$.{LocalizedValueObject.DefaultKey}"));

            var localizedOrDefaultExpression = Expression.Coalesce(
                localizedExpression,
                defaultExpression);

            var result = (customReturnType != null)
                ? Expression.Convert(localizedOrDefaultExpression, customReturnType) as Expression
                : localizedOrDefaultExpression;

            return(result);
        }