In our last post Separating your ODATA Models from the Persistence Layer with AutoMapper I showed how we can separate our public API Model from the internal persistence layer. In this post I will show how we can re-model the public API model without changing the internal data model.

In the example from the previous post I showed our model Customer that contained a few properties regarding the creation and modification of the entity:

public class Customer
{
  [Key]
  public long Id { get; set; }

  [Required]
  public string Name { get; set; }

  public string Description { get; set; }

  [Required]
  public string ContractId { get; set; }

  [Required]
  public long CreatedById { get; set; }

  [ForeignKey(nameof(ModifiedById))]
  public User ModifiedBy { get; set; }

  [Required]
  public long ModifiedById { get; set; }

  [ForeignKey(nameof(ModifiedById))]
  public User ModifiedBy { get; set; }

  [Required]
  public DateTimeOffset Created { get; set; }

  [Required]
  public DateTimeOffset Modified { get; set; }

  [Timestamp]
  public byte[] RowVersion { get; set; }
}

However it seems that most of the information is not really necessary or useful for the caller. Therefore I would like to hide these properties in a normal response and only reveal them upon sepcific request via an ODATA $expand option.

This is possible with the help of AutoMapper. We just have to refactor our model to this:

public class EntityDetails
{
  [Required]
  public long CreatedById { get; set; }

  [Required]
  public long ModifiedById { get; set; }

  [Required]
  public DateTimeOffset Created { get; set; }

  [Required]
  public DateTimeOffset Modified { get; set; }

  [Timestamp]
  public byte[] RowVersion { get; set; }
}

public class Customer
{
  [Key]
  public long Id { get; set; }

  [Required]
  public string Name { get; set; }

  public string Description { get; set; }

  [Required]
  public string ContractId { get; set; }

  public EntityDetails Details { get; set; }
}

Now our model contains a subclass with the required information. In order to map the information from the underlying data access model we have to extend our mapping profile (from the previous post) to this:

public class EntityAutoMapperProfile : Profile
{
  public EntityAutoMapperProfile()
  {
    CreateMap<CustomerDataAccess, Customer>()
    .ForMember(dest => dest.Details, opt => opt.MapFrom(src => new EntityDetails
    {
      CreatedById = src.CreatedById,
      ModifiedById = src.ModifiedById,
      Created = src.Created,
      Modified = src.Modified,
      RowVersion = src.RowVersion,
    }))
    .ReverseMap();
  }
}

With this mapping we transform the separate properties from the data access entity to the EntityDetails class (we just have to make sure that we use a Lambda Expression).

The only thing that is missing is to tell ODATA that EntityDetails is a known entity, otherwise we will get an error similar to this:

message=No NavigationLink factory was found for the navigation property 
'Details' from entity type 'Net.Appclusive.Public.Domain.EntityDetails' 
on entity set 'Customers'. Try calling HasNavigationPropertyLink on the 
EntitySetConfiguration.

Parameter name: navigationProperty

For more information on this error have a look at ODataController throws ‘No NavigationLink factory was found’ when returning a different type in IHttpActionResult.

void Initialise(System.Web.Http.OData.Builder.ODataConventionModelBuilder builder)
{
  builder.ComplexType<EntityDetails>();
}

Note: if you use builder.EntitySet("EntityDetails") the retrieval/return of these objects works as well, but we cannot use these objects to post/send them from the client to the server.

After adding the line above to our model builder we can query our entity set via Invoke-RestMethod 'http://appclusive/api/Core/Customers(1)?$expand=Details' and get the following output:

{
  "odata.metadata":  "http://appclusive/api/Core/$metadata#Customers/@Element",
  "Id":  "1",
  "Name": "Edgar Schnittenfittich",
  "Description":  "Our first customer"
  "ContractId":  "arbitrary-contract-id",
  "Details":
  {
    "CreatedById":  "1",
    "ModifiedById":  "1",
    "Created":  "2016-08-15T05:42:08.01Z",
    "Modified":  "2017-01-01T08:05:42.01Z",
    "RowVersion":  "AAAAAAABvVY="
  }
}

And when querying the entity without any options it just returns:

{
  "odata.metadata":  "http://appclusive/api/Core/$metadata#Customers/@Element",
  "Id":  "1",
  "Name": "Edgar Schnittenfittich",
  "Description":  "Our first customer"
  "ContractId":  "arbitrary-contract-id"
}

With this approach we can now freely modify the layout of our public models without changing the internal database models and schema.

4 Comments »

    • I am not quite sure if I understand exactly what you mean. We have models where we use self referencing (to build a tree). I can post youi the code for this if you are interested.

  1. I am not quite sure if I understand exactly what you mean. We have models where we use self referencing (to build a tree). I can post youi the code for this if you are interested.

Leave a Reply to Ronald Rink Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.