private async Task <object> ExecAndReturnResponseAsync <Table>(object dto, IDbConnection db, Func <ExecContext, Task <ExecValue> > fn)
        {
            var responseType   = HostContext.Metadata.GetOperation(dto.GetType())?.ResponseType;
            var responseProps  = responseType == null ? null : TypeProperties.Get(responseType);
            var idProp         = responseProps?.GetAccessor(Keywords.Id);
            var countProp      = responseProps?.GetAccessor(Keywords.Count);
            var resultProp     = responseProps?.GetAccessor(Keywords.Result);
            var rowVersionProp = responseProps?.GetAccessor(Keywords.RowVersion);

            var execValue = await fn(new ExecContext(idProp, resultProp, countProp, rowVersionProp));

            if (responseType == null)
            {
                return(null);
            }

            object idValue = null;

            var response = responseType.CreateInstance();

            if (idProp != null && execValue.Id != null)
            {
                idValue = execValue.Id.ConvertTo(idProp.PropertyInfo.PropertyType);
                idProp.PublicSetter(response, idValue);
            }
            if (countProp != null && execValue.RowsUpdated != null)
            {
                countProp.PublicSetter(response, execValue.RowsUpdated.ConvertTo(countProp.PropertyInfo.PropertyType));
            }

            if (resultProp != null && execValue.Id != null)
            {
                var result = await db.SingleByIdAsync <Table>(execValue.Id);

                resultProp.PublicSetter(response, result.ConvertTo(resultProp.PropertyInfo.PropertyType));
            }

            if (rowVersionProp != null)
            {
                if (AutoMappingUtils.IsDefaultValue(idValue))
                {
                    var modelDef    = typeof(Table).GetModelMetadata();
                    var dtoIdGetter = TypeProperties.Get(dto.GetType()).GetPublicGetter(modelDef.PrimaryKey.Name);
                    if (dtoIdGetter != null)
                    {
                        idValue = dtoIdGetter(dto);
                    }
                }
                if (AutoMappingUtils.IsDefaultValue(idValue))
                {
                    throw new NotSupportedException($"Could not resolve Primary Key from '{dto.GetType().Name}' to be able to resolve RowVersion");
                }

                var rowVersion = await db.GetRowVersionAsync <Table>(idValue);

                rowVersionProp.PublicSetter(response, rowVersion.ConvertTo(rowVersionProp.PropertyInfo.PropertyType));
            }

            return(response);
        }
        private async Task <object> UpdateInternalAsync <Table>(object dto, bool skipDefaults)
        {
            using var db = AutoQuery.GetDb <Table>(Request);
            using (Profiler.Current.Step("AutoQuery.Update"))
            {
                var response = await ExecAndReturnResponseAsync <Table>(dto, db,
                                                                        async ctx => {
                    var dtoValues  = ResolveDtoValues(dto, skipDefaults);
                    var pkFieldDef = typeof(Table).GetModelMetadata()?.PrimaryKey;
                    if (pkFieldDef == null)
                    {
                        throw new NotSupportedException($"Table '{typeof(Table).Name}' does not have a primary key");
                    }
                    if (!dtoValues.TryGetValue(pkFieldDef.Name, out var idValue) || AutoMappingUtils.IsDefaultValue(idValue))
                    {
                        throw new ArgumentNullException(pkFieldDef.Name);
                    }

                    // Should only update a Single Row
                    var rowsUpdated = GetAutoFilterExpressions(db, dto, dtoValues, Request, out var expr, out var exprParams)
                            ? await db.UpdateOnlyAsync <Table>(dtoValues, expr, exprParams.ToArray())
                            : await db.UpdateOnlyAsync <Table>(dtoValues);

                    if (rowsUpdated != 1)
                    {
                        throw new OptimisticConcurrencyException($"{rowsUpdated} rows were updated by '{dto.GetType().Name}'");
                    }

                    return(new ExecValue(idValue, rowsUpdated));
                });     //TODO: UpdateOnly

                return(response);
            }
        }
        private Dictionary <string, object> ResolveDtoValues(object dto, bool skipDefaults = false)
        {
            var dtoValues = dto.ToObjectDictionary();

            var meta = AutoCrudMetadata.Create(dto.GetType());

            if (meta.MapAttrs != null)
            {
                foreach (var entry in meta.MapAttrs)
                {
                    if (dtoValues.TryRemove(entry.Key, out var value))
                    {
                        dtoValues[entry.Value.To] = value;
                    }
                }
            }

            var appHost = HostContext.AppHost;

            if (skipDefaults || meta.UpdateAttrs != null || meta.DefaultAttrs != null)
            {
                List <string> removeKeys = null;
                Dictionary <string, object> replaceValues = null;

                foreach (var entry in dtoValues)
                {
                    var isNullable     = meta.NullableProps?.Contains(entry.Key) == true;
                    var isDefaultValue = entry.Value == null || (!isNullable && AutoMappingUtils.IsDefaultValue(entry.Value));
                    if (isDefaultValue)
                    {
                        var handled = false;
                        if (meta.DefaultAttrs != null && meta.DefaultAttrs.TryGetValue(entry.Key, out var defaultAttr))
                        {
                            handled = true;
                            replaceValues ??= new Dictionary <string, object>();
                            replaceValues[entry.Key] = appHost.EvalScriptValue(defaultAttr, Request);
                        }
                        if (!handled)
                        {
                            if (skipDefaults ||
                                (meta.UpdateAttrs != null && meta.UpdateAttrs.TryGetValue(entry.Key, out var attr) &&
                                 attr.Style == AutoUpdateStyle.NonDefaults))
                            {
                                removeKeys ??= new List <string>();
                                removeKeys.Add(entry.Key);
                            }
                        }
                    }
                }

                if (removeKeys != null)
                {
                    foreach (var key in removeKeys)
                    {
                        dtoValues.RemoveKey(key);
                    }
                }

                if (replaceValues != null)
                {
                    foreach (var entry in replaceValues)
                    {
                        dtoValues[entry.Key] = entry.Value;
                    }
                }
            }

            if (meta.PopulateAttrs != null)
            {
                foreach (var populateAttr in meta.PopulateAttrs)
                {
                    dtoValues[populateAttr.Field] = appHost.EvalScriptValue(populateAttr, Request);
                }
            }

            var populatorFn = AutoMappingUtils.GetPopulator(
                typeof(Dictionary <string, object>), meta.DtoType);

            populatorFn?.Invoke(dtoValues, dto);

            // Ensure RowVersion is always populated if defined on Request DTO
            if (meta.RowVersionGetter != null && !dtoValues.ContainsKey(Keywords.RowVersion))
            {
                dtoValues[Keywords.RowVersion] = default(uint);
            }

            return(dtoValues);
        }