Stored procedures ontsluiten met OData service operations

OData is een heel interessant communicatie middel om informatie uit te
wisselen. De grootste vernieuwing is dat de client, niet de server, uitmaakt
welke data over het lijntje gaat. Waar ‘klassieke’ webservices een keur aan
methodes beschikbaar stellen om alle mogelijke selecties te ondersteunen, heeft
OData voldoende aan het ontsluiten van een context met entiteiten.

Maar zoals altijd moeten nieuwe technieken aansluiten op al bestaande
oplossingen. Voor OData geldt hetzelfde. In deze blog laat ik zien hoe OData
gebruikt kan worden om stored procedures te ontsluiten.

OData ontsluiten

Hoewel inhoudelijk niet zo relevant, laat ik eerst even het model van
entiteiten die al door een OData service ontsloten kunnen worden.

datamodel

Deze entiteiten zijn een directe afgeleide van drie tabellen in de
database. Maar in diezelfde database is ook een stored procedure beschikbaar
welke de machines toont die een minimum aantal storingen
hebben.


CREATE PROCEDURE USP_Machines_With_Minimum_Amount_Of_Failures

@MinimumCount int = 99

AS

BEGIN

SELECT M.MachineId, M.MachineName, M.SupervisorName

FROM Machines M

WHERE @MinimumCount <=

( SELECT COUNT(P.PartId)

FROM Parts P

JOIN Failures F ON P.MachineId = M.MachineId

AND P.PartId = F.PartId )

END

Merk op dat we als resultaat een lijst van records teruggeven waarvan
de opmaak niet 1-op-1 overeenkomt met de Machine entiteit. We missen enkele kolommen.

Voor de te bouwen WCF Data Service maken we hier gebruik van een
Entity Framework model. We voegen dus een EDMX toe aan een webproject en geven aan het model de opdracht om de drie tabellen en de stored procedure te gaan representeren.

EDMX

De stored procedure moet hierbij opnomen zijn om de volgende stap
te kunnen maken. Stored procedures die niet aan het model kenbaar zijn gemaakt in deze stap, kunnen later ook niet geselecteerd worden.

Als we de model-browser openen dan zien we ook netjes dat de stored
procedure opgenomen is:

Model browser

Maar we zijn hiermee nog niet klaar met het model. De stored procedure is nu
nog niet beschikbaar in de context. We moeten nu een function import uitvoeren
zodat de context de stored procedure in entiteiten kan uitdrukken.  Een
rechtermuis klik op de EDMX moet voldoende zijn om de function import te
starten:

Function import

Dit geeft een wizard waarbij een aantal zaken ingesteld moeten worden.

Function import wizard

Eerst geven we bovenaan een nettere naam aan de functie. En we kunnen onze
stored procedure selecteren (..)

Vervolgens moeten we de te retourneren waarden vaststellen en hierbij
moeten we goed opletten. Omdat de stored procedure geen complete entiteiten
retourneert, gaan we een nieuw complex type genereren.

Druk hiervoor op de “Get Column Information” knop. De te retourneren
waarden worden vastgesteld uit de stored procedure . Druk vervolgens op de knop “Create new complex type”:

Wizard part 2

En kies als laatste in het midden van het scherm voor het retourneren van
een lijst van het nieuw gegenereerde complex type (te herkennen aan de naam met _Result achter de stored procedure naam).

Function imported

Let op: Het return type moet bij de eigenschappen ingesteld zijn!

Nu is de stored procedure als functie beschikbaar in de context. We gaan dus
over op het aanmaken van OData service.

De volgende stap is dus het toevoegen  van een WCF Data service.

public class WcfDataService1 : DataService<SdnODataEntities>
{
  public static void InitializeService(DataServiceConfiguration config)
  {
    config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
    config.SetEntitySetPageSize("*", 10);
    config.DataServiceBehavior.MaxProtocolVersion =
      DataServiceProtocolVersion.V2;
  }
}

Als we deze service controleren op het bestaan van het nieuwe complex type,
dan blijkt dat er nog niks is veranderd, voor de service zelf. Alleen de drie
entiteiten zijn nog steeds direct te benaderen:

<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<service xml:base="htt p://localhost:2239/WcfDataService1.svc/"
    xmlns:atom="htt p://www.w3.org/2005/Atom"
    xmlns:app="htt p://www.w3.org/2007/app"
    xmlns="htt p://www.w3.org/2007/app">
  <workspace>
    <atom:title>Default</atom:title>
    <collection href="Failures">
      <atom:title>Failures</atom:title>
    </collection>
    <collection href="Machines">
      <atom:title>Machines</atom:title>
    </collection>
    <collection href="Parts">
      <atom:title>Parts</atom:title>
    </collection>
  </workspace>
</service>

Het nieuwe type is dus niet te kiezen en zal ook nooit zo direct
beschikbaar zijn. En ook via de metadata is het type (nog) niet
controleerbaar.

Maar de stored procedure is wel beschikbaar binnen de context en deze gaan we
als Service Operation ontsluiten:

public static void InitializeService(
DataServiceConfiguration config)
{
  config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
  config.SetEntitySetPageSize("*", 10);
  config.DataServiceBehavior.MaxProtocolVersion
     = DataServiceProtocolVersion.V2;
  config.SetServiceOperationAccessRule(
     "GetMachinesWithMinimumCountOfFailures",
     ServiceOperationRights.AllRead);
}

[WebGet]
public List<MachinesWithMinimumCountOfFailures_Result>
        GetMachinesWithMinimumCountOfFailures(int minimumCount)
{
  return this.CurrentDataSource.
        MachinesWithMinimumCountOfFailures(minimumCount).ToList();
}

We maken dus een methode aan die met een WebGet attribuut opgemaakt is.
En deze service operation is voor alle leesacties beschikbaar.

Als we nu de OData service starten en de metadata controleren (http://localhost:2239/WcfDataService1.svc/$metadata)
dan komt alles beschikbaar:

<ComplexType Name="MachinesWithMinimumCountOfFailures_Result">
  <Property Name="MachineId"
            Type="Edm.Int32"
            Nullable="false" />
  <Property Name="MachineName"
            Type="Edm.String"
            Nullable="false"
            MaxLength="50" />
  <Property Name="SupervisorName"
            Type="Edm.String"
            Nullable="false"
            MaxLength="50" />
</ComplexType>
...
<FunctionImport Name="GetMachinesWithMinimumCountOfFailures"
                ReturnType="Collection(ODataModel.MachinesWithMinimumCountOfFailures_Result)"
                m:HttpMethod="GET">
   <Parameter Name="minimumCount"
              Type="Edm.Int32"
              Mode="In" />
</FunctionImport>

Mooi. Als we nu een selectie uitvoeren via de service operation…
http://localhost/WcfDataService1.svc/GetMachinesWithMinimumCountOfFailures?minimumCount=1

…dan krijgen we netjes alles informatie in het formaat van de complex
type terug.

<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<GetMachinesWithMinimumCountOfFailures
  xmlns="htt p://schemas.microsoft.com/ado/2007/08/dataservices">
  <element p2:type="ODataModel.MachinesWithMinimumCountOfFailures_Result"
      xmlns:p2="htt p://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
    <MachineId p2:type="Edm.Int32">1</MachineId>
    <MachineName>MixerNew</MachineName>
    <SupervisorName>user</SupervisorName>
  </element>

  <element p2:type="ODataModel.MachinesWithMinimumCountOfFailures_Result"
      xmlns:p2="htt p://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
    <MachineId p2:type="Edm.Int32">2</MachineId>
    <MachineName>Machine that goes Ping</MachineName>
    <SupervisorName>user</SupervisorName>
  </element>

  <element p2:type="ODataModel.MachinesWithMinimumCountOfFailures_Result"
      xmlns:p2="htt p://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
    <MachineId p2:type="Edm.Int32">7</MachineId>
    <MachineName>Mr. Coffee</MachineName>
    <SupervisorName>Dark Helmet</SupervisorName>
  </element>

  <element p2:type="ODataModel.MachinesWithMinimumCountOfFailures_Result"
      xmlns:p2="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
    <MachineId p2:type="Edm.Int32">17</MachineId>
    <MachineName>Rage against the machine</MachineName>
    <SupervisorName>user</SupervisorName>
  </element>

  <element p2:type="ODataModel.MachinesWithMinimumCountOfFailures_Result"
      xmlns:p2="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
    <MachineId p2:type="Edm.Int32">18</MachineId>
    <MachineName>Mr. Radar</MachineName>
    <SupervisorName>Dark Helmet</SupervisorName>
  </element>

</GetMachinesWithMinimumCountOfFailures>

We hebben nu gezien dat het heel eenvoudig is om bestaande stored
procedures in een OData service op te nemen. Natuurlijk had deze logica ook
zonder de stored procedure dooe een client achterhaald kunnen worden maar voor meer complexere en specialistische selecties kan dit een uitkomst zijn.

Overigens wil ik nog even wijzen op een aardige bron van informatie
rond OData. Ik kan OData Primer aanbevelen vanwege de constant stroom van interessante links. Ik heb mijzelf op de RSS feed geabbonneerd en zojuist kwam (tijdens het schrijven van deze blog) een verwijziging voorbij naar een andere blog waarin bovenstaande techniek vergelijkbaar beschreven werd. Blijkbaar is het onsluiten van stored procedures een hot item 🙂

Advertenties