Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET OData - Implementing PATCH with Delta<T> with Data Transfer Objects

In my OData controller I'm converting my EF entities to a DTO because the entity contains many fields which just aren't used by the UI.

This question and answer (ASP.NET WebApi OData support for DTOs) shows how I can apply the query options from the OData URI to the the EF query and return DTOs. This is great, it means I'm getting the benefit of querying the DB and also the benefit of serialising smaller entities.

However, how do I then apply the Delta with my patched fields to my entity when I need to update it?

The field names in the entity don't match the DTO.

I can use the changed fields collection from the Delta but then I'd have map all the field names and use reflection to update all the properties in the Entity.

Is there a better way?

Should I use my entity instead of the DTO and use the odata $select parameter to reduce the size of data on the wire.

Should I just revert back to WebAPI and have individual update functions which only take the parameters that are needed, for example UpdateStartDate(int id, DateTime newStartDate)

like image 751
BenCr Avatar asked Nov 14 '13 13:11

BenCr


1 Answers

I have just ran into the same problem and found the following link to be helpful: http://qimata.com/?p=1381

For the sake of prosperity, here is the code which uses AutoMapper to map the Database entity to a DTO, you then apply the Patching to the DTO objects, and then use AutoMapper to map back to the database entity before saving:

[AcceptVerbs("PATCH", "MERGE")]
public virtual async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<DtoEntity> delta, CancellationToken cancellationToken)
{
    Validate(delta.GetEntity());

    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var entity = await _genericRepository.FindAsync(cancellationToken, key);
    var dto = Mapper.Map<DtoEntity>(entity);
    delta.Patch(dto);
    Mapper.Map(dto, entity);
    await _context.SaveChangesAsync(cancellationToken);
    return Updated(dto);
}

One other thing worth mentioning is that when using AutoMapper with EntityFramework, be careful of automatic expansions of navigation properties. You can disable expansions by using the ExplicitExpansion method:

Mapper.CreateMap<DbEntity, DtoEntity>()
            .ForMember(dest => dest.Example, opt => opt.ExplicitExpansion());

I also had to disable Lazy Loading with Configuration.LazyLoadingEnabled = false; in my DbContext Constructor.

like image 121
philreed Avatar answered Nov 19 '22 04:11

philreed