/// <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); }