Exemplo n.º 1
0
        /// <summary>
        ///     Updates the models specified in the request body by their primary key
        /// </summary>
        /// <param name="context">The current API context</param>
        /// <param name="dataset">The filtered dataset to operate on</param>
        /// <returns>The affected models</returns>
        public virtual async Task <IEnumerable <TModel> > OperateAsync(
            IApiContext <TModel, object> context,
            IQueryable <TModel> dataset)
        {
            TContext DatabaseContext = context.Services.GetRequiredService <TContext>();

            // todo: make better
            List <TModel> UpdatedList = new List <TModel>();

            if (context.Parsed == null)
            {
                throw new OperationFailedException("Must have a parsed body to update");
            }

            foreach (ParseResult <TModel> Result in context.Parsed)
            {
                // by default assume the properties were parsed with the rest of the model
                // todo: this is the ugliest thing i have ever seen
                object?[] Values = this.ParameterDelegates == null
                                                           ? this.Properties.Select(p => p.GetGetMethod()?.Invoke(Result.ParsedModel, null))
                                   .ToArray()
                                                           : this.Properties.Zip(this.ParameterDelegates).Select(
                    v => ParameterResolver.ParseParameter(
                        v.Second.GetValue(context.Request),
                        v.First.PropertyType)).ToArray();

                if (Values.Any(v => v == null))
                {
                    throw new OperationFailedException(
                              "Missing expected value for update operation",
                              new ArgumentNullException(
                                  this.Properties[Array.FindIndex(Values, v => v == null)].Name,
                                  "Parameter was null"), 400);
                }

                // so that ef can still use sql queries, let's generate an expression tree for it to use
                ParameterExpression ModelParameter = Expression.Parameter(typeof(TModel));

                // generate expressions. boils down to: ModelParameter.Property == Values[i]
                Expression[] ComparisonExpressions = this.Properties.Zip(Values).Select(
                    v => (Expression)Expression.Equal(
                        Expression.Property(ModelParameter, v.First),
                        Expression.Constant(v.Second))).ToArray();

                // && them all together
                Expression AggregateExpression = ComparisonExpressions.Aggregate(Expression.AndAlso);

                // and create the lambda
                Expression <Func <TModel, bool> > FilterExpression =
                    Expression.Lambda <Func <TModel, bool> >(AggregateExpression, ModelParameter);

                IEnumerable <TModel> ExistingModels = dataset.Where(FilterExpression);

                foreach (TModel Existing in ExistingModels)
                {
                    foreach (PropertyInfo Updated in Result.PresentProperties)
                    {
                        object?NewValue = Updated.GetGetMethod()?.Invoke(Result.ParsedModel, null);
                        Updated.GetSetMethod()?.Invoke(Existing, new[] { NewValue });
                    }

                    UpdatedList.Add(Existing);

                    // TODO: obviously it's 1000x better to update all at once. do that
                    await DatabaseContext.UpdateAsync(Existing);
                }
            }

            return(UpdatedList);
        }