Как исправить в Web API и OData


Из чтения спецификации RFC глагола Patch ясно, что глагол Patch должен получать значения не для частичного обновления сущности, а для выполнения операций:

...Вместе с патчем, однако, вложенная сущность содержит набор инструкций, описывающих, как ресурс в настоящее время находится на исходный сервер должен быть изменен для создания новой версии.

В MSDN для класса Delta это также понятно, как и описание патча говорит:

Перезаписывает исходную сущность с помощью изменений, отслеживаемых этой Дельтой.

В отличие от описания ставим :

Перезаписывает исходную сущность значениями , хранящимися в этой дельте.

Пока все хорошо, но я не мог найти способ отправить эти "инструкции" с OData, независимо от того, что я делаю, Дельта.Патч только заменяет значения.

Каким должен быть синтаксис запроса на исправление?

Способы, которые я пробовал, были:

PATCH http://localhost:55783/Products(1) HTTP/1.1
User-Agent: Fiddler
Host: localhost:55783
Content-Length: 19
Content-type: application/json

{ "Price": 432 }

И

{ "op": "add", "path": "/Price", "value": 423432 }

И все такое прочее.


Обновление:

Благодаря Майклу Муру и прочитав весь класс Delta с ILSpy, я думаю, что это действительно ошибка в дизайне глагола Patch.
я открыл ошибку для Microsoft, вы можете проголосовать за нее, если вам нужно, чтобы она тоже была исправлена.

1 7

1 ответ:

Я не уверен, что то, чего вы пытаетесь достичь, возможно. По крайней мере, не с Delta<TEntity>.Patch(..)

Предполагая, что у вас есть Product сущность и где-то в вашем PATCH действии у вас есть

[AcceptVerbs("PATCH")]
public void Patch(int productId, Delta<Product> product)
{
    var productFromDb = // get product from db by productId
    product.Patch(productFromDb);
    // some other stuff
}

Когда product создается, внутренне он вызывает Delta<TEntityType> конструктор, который выглядит так (конструктор без параметров также вызывает этот, передавая typeof(TEntityType)

public Delta(Type entityType)
{
    this.Initialize(entityType);
}

Initialize метод выглядит следующим образом

private void Initialize(Type entityType)
{
    // some argument validation, emitted for the sake of brevity 

    this._entity = (Activator.CreateInstance(entityType) as TEntityType);
    this._changedProperties = new HashSet<string>();
    this._entityType = entityType;
    this._propertiesThatExist = this.InitializePropertiesThatExist();
}

Интересной частью здесь является this._propertiesThatExist, которая является Dictionary<string, PropertyAccessor<TEntityType>>, которая содержит свойства типа продукта. PropertyAccessor<TEntityType> - это внутренний тип, позволяющий легче манипулировать свойствами.

Когда вы звоните product.Patch(productFromDb) это то, что происходит под капотом

// some argument checks
PropertyAccessor<TEntityType>[] array = (
        from s in this.GetChangedPropertyNames()
        select this._propertiesThatExist[s]).ToArray<PropertyAccessor<TEntityType>>();

    PropertyAccessor<TEntityType>[] array2 = array;

    for (int i = 0; i < array2.Length; i++)
    {
        PropertyAccessor<TEntityType> propertyAccessor = array2[i];
        propertyAccessor.Copy(this._entity, original);
    }

Как вы можете видеть, он получает свойства, которые были изменены, перебирает их и устанавливает значения из экземпляра, который был передан в действие Patch, в экземпляр, который вы получаете из БД. Таким образом, передаваемая операция, имя свойства и добавляемое значение ничего не будут отражать.

propertyAccessor.Copy(this._entity, original) тело метода

public void Copy(TEntityType from, TEntityType to)
{
    if (from == null)
    {
        throw Error.ArgumentNull("from");
    }
    if (to == null)
    {
        throw Error.ArgumentNull("to");
    }
    this.SetValue(to, this.GetValue(from));
}