Web API OData V4 Using Contained Models Part 6

This post is part 6 of the Web API and OData V4 series. This article shows how Web API 2.2 with OData V4 can use contained EntityTypes in an OData model without defining the entities on the top level. In this example, the Player entity is the route entity set. The PlayerStats can then be accessed though it’s parent entity. i.e. The PlayerStats entity is contained in Player.

Part 1 Getting started with Web API and OData V4 Part 1.
Part 2 Web API and OData V4 Queries, Functions and Attribute Routing Part 2
Part 3 Web API and OData V4 CRUD and Actions Part 3
Part 4 Web API OData V4 Using enum with Functions and Entities Part 4
Part 5 Web API OData V4 Using Unity IoC, SQLite with EF6 and OData Model Aliasing Part 5
Part 6 Web API OData V4 Using Contained Models Part 6
Part 7 Web API OData V4 Using a Singleton Part 7
Part 8 Web API OData V4 Using an OData T4 generated client Part 8
Part 9 Web API OData V4 Caching Part 9
Part 10 Web API OData V4 Batching Part 10
Part 11 Web API OData V4 Keys, Composite Keys and Functions Part 11

Code: https://github.com/damienbod/WebAPIODataV4SQLite

Creating the new SQLite entities

The two entity types which will be used by the OData model need to be created and added to the Entity Framework domain model. Two tables are created in the SQLite database:

Create a Player table in the SQLite database (App_Data Folder)

CREATE TABLE "Player"
(
 "Id" INTEGER PRIMARY KEY  AUTOINCREMENT  NOT NULL  UNIQUE ,
 "Name" TEXT NOT NULL  DEFAULT Unknown,
 "Email" TEXT NOT NULL  DEFAULT Unknown
)

I use SQLite Manager, but any SQLite Tool can be used.
odataV406_01

And the PlayerStats table:

CREATE TABLE "PlayerStats"
(
 "PlayerStatsId" INTEGER PRIMARY KEY  NOT NULL ,
 "SkillLevel" int NOT NULL ,
 "HighScore" NVARCHAR(160) NOT NULL ,
 "PlayerId" INTEGER NOT NULL ,
 FOREIGN KEY ([PlayerId]) REFERENCES [Player] ([Id])
   ON DELETE NO ACTION ON UPDATE NO ACTION
)

Now create the 2 entities in the application. The [Key, ForeignKey(“Player”)] needs to be defined for the Player in the PlayerStats. The entities have a one to one association and the direction needs to be defined. The PlayerStats property uses the OData Attribute Contained. This is required for the OData model.

using System.ComponentModel.DataAnnotations;
using System.Web.OData.Builder;

namespace WebAPIODataV4SQLite.DomainModel
{
    public class Player
    {
        [Key]
        public long Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }

        [Contained]
        public virtual PlayerStats PlayerStats { get; set; }
    }

    public class PlayerStats
    {
        public long PlayerStatsId { get; set; }
        public int SkillLevel { get; set; }
        public string HighScore { get; set; }

        [Key, ForeignKey("Player")]
        public long PlayerId { get; set; }
        public virtual Player Player { get; set; }
    }
}

Now the new entites can be added to the Entity Framework context:

public DbSet<PlayerStats> PlayerStatsEntities { get; set; }
public DbSet<Player> PlayerEntities { get; set; }

The Entites are ready to be used in the OData model. Add them to the odata model.

builder.EntitySet<Player>("Player");
builder.EntityType<PlayerStats>();

Now an ODataController for Player can be created. This is the top level OData entity. The controller contains a method to access the contained PlayerStats (GetPlayserStats). This method returns the PlayerStats for it’s parent Player entity.

using System.Linq;
using System.Web.Http;
using System.Web.OData;
using System.Web.OData.Routing;
using WebAPIODataV4SQLite.DomainModel;

namespace WebAPIODataV4SQLite.Controllers
{
    public class PlayerController : ODataController
    {
        readonly SqliteContext _sqliteContext;

        public PlayerController(SqliteContext sqliteContext)
        {
            _sqliteContext = sqliteContext;
        }

        [EnableQuery(PageSize = 20)]
        public IHttpActionResult Get()
        {
            return Ok(_sqliteContext.PlayerEntities.AsQueryable());
        }

        [EnableQuery(PageSize = 20)]
        public IHttpActionResult Get([FromODataUri] int key)
        {
            return Ok(_sqliteContext.PlayerEntities.Find(key));
        }

        [EnableQuery(PageSize = 20)]
        [ODataRoute("Player({key})/PlayerStats")]
        public IHttpActionResult GetPlayserStats([FromODataUri] int key)
        {
            return Ok(_sqliteContext.PlayerEntities.Find(key).PlayerStats);
        }
    }
}

Now the Contained entity can be used:

http://localhost:59145/odata/Player(1)/PlayerStats

http://localhost:59145/odata/Player(1)?$expand=PlayerStats

odataV406_02

Links:

http://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/OData/v4/ODataContainmentSample/

http://blogs.msdn.com/b/odatateam/archive/2014/03/13/containment-is-coming-with-odata-v4.aspx

http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-22#OData

Leave a comment

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