Telerik Data Access and WebApi OData v4

4 posts, 0 answers
  1. Paul
    Paul avatar
    17 posts
    Member since:
    May 2011

    Posted 20 Jan 2015 Link to this post

    Hi,

    I'm trying to build an OData v4 service using the Microsoft-provided API (tutorial), with the data services layer provided by Telerik Data Access.  I have moderately complex data model, which includes a few different cases of inheritance.

    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public IList<Address> Addresses { get; set; }
    }
    public class Teacher : Person
    {
        public IList<TeachingVenue> TeachingVenues { get; set; }
    }
    public class Address
    {
        public int Id { get; set; }
        public string AddressText { get; set; }
        public string City { get; set; }
        public string Province { get; set; }
        public string Country { get; set; }
    }
    public class TeachingVenue
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    public class Lab : TeachingVenue
    {
        public string Facilities { get; set; }
    }
    public class Classroom : TeachingVenue
    {
        public string Building { get; set; }
    }

    This is registered in my model like this:
    protected override IList<MappingConfiguration> PrepareMapping()
    {
        List<MappingConfiguration> mappingConfigurations = new List<MappingConfiguration>();
     
        var people = new MappingConfiguration<Person>();
        people.MapType().ToTable("People");
        people.HasDiscriminator().ToColumn("Type");
        people.HasDiscriminatorValue("people");
        people.HasProperty(p => p.Id).IsIdentity(KeyGenerator.Autoinc);
        people.HasAssociation(p => p.Addresses)
            .ToColumn("PersonId", "Addresses");
        mappingConfigurations.Add(people);
     
        var teachers = new MappingConfiguration<Teacher>();
        teachers.MapType().Inheritance(InheritanceStrategy.Flat).ToTable("People");
        teachers.HasDiscriminatorValue("teacher");
        teachers.HasAssociation(p => p.TeachingVenues)
            .ToColumn("TeacherId", "TeachingVenues");
        mappingConfigurations.Add(teachers);
     
        var addresses = new MappingConfiguration<Address>();
        addresses.MapType().ToTable("Addresses");
        addresses.HasProperty(a => a.Id).IsIdentity(KeyGenerator.Autoinc);
        mappingConfigurations.Add(addresses);
     
        var labsorclassrooms = new MappingConfiguration<TeachingVenue>();
        labsorclassrooms.MapType().ToTable("TeachingVenues");
        labsorclassrooms.HasDiscriminator().ToColumn("Type");
        labsorclassrooms.HasProperty(lc => lc.Id).IsIdentity(KeyGenerator.Autoinc);
        mappingConfigurations.Add(labsorclassrooms);
     
        var classrooms = new MappingConfiguration<Classroom>();
        classrooms.MapType().Inheritance(InheritanceStrategy.Flat).ToTable("TeachingVenues");
        classrooms.HasDiscriminatorValue("class");
        mappingConfigurations.Add(classrooms);
     
        var labs = new MappingConfiguration<Lab>();
        labs.MapType().Inheritance(InheritanceStrategy.Flat).ToTable("TeachingVenues");
        labs.HasDiscriminatorValue("lab");
        mappingConfigurations.Add(labs);
     
        return mappingConfigurations;
    }

    The interesting feature here is that I have a derived class with a navigation property with a collection of derived classes.

    I have written a simple OData controller to query for Teachers:
    public class TeachersController : ODataController
    {
        protected TelerikModel context = new TelerikModel();
     
        [EnableQuery]
        public SingleResult<Teacher> Get(int key)
        {
            var query = context.GetAll<Teacher>().Where(p => p.Id == key);
            return SingleResult.Create(query);
        }
    }

    Everything works fine - until I attempt to query my OData service with an "$expand" directive:

    GET http://localhost/odata/Teachers(1)?$expand=Addresses

    This query will result in a NullReferenceException from within the Telerik code:

    Object reference not set to an instance of an object.
       at OpenAccessRuntime.Relational.fetch.FopGetProjection.init(SelectExp root)

    The error doesn't come up if I don't have the TeachingVenues collection defined.  The error doesn't come up if I am querying the data context directly.  The error doesn't come up if Teacher is the base class.

    Is there something I'm missing?

    Paul S.
  2. Viktor Zhivkov
    Admin
    Viktor Zhivkov avatar
    291 posts

    Posted 23 Jan 2015 Link to this post

    Hello Paul,

    Currently we are not offering OData support for Web API controllers using directly Telerik Data Access LINQ queries. Direct adaptation between samples from EF (as shown in Web API tutorials) to Telerik Data Access will not always work and sometime may have some unexpected effects.
    In this particular scenario you are hitting an optimization in our runtme that recognizes that you want to get a single object by it's primary key so there won't be a real LINQ query, but rather and optimized point-fetch. Unfortunately this means that your expand statement cannot be applied properly resulting in a failure/exception.
    My suggestion is to implement special handling for OData-related query parameters and apply each one manually to the LINQ query, then execute it via .ToList() and detach the result (via OpenAccessContext.CreateDetachedCopy() API).
    The last step is required as Telerik Data Access always uses lazy loading and you may get a really large graph of object loaded during the serialization of the results as unwelcome side effect.
    If you haven't already tried this, you can check the generated code from Add Service wizard as it can show you how to handle the detaching of objects. In addition to that there is a sample showing how to integrate Kendo UI and our generated Web API code, with some limited OData support. You can find that sample in our Samples Kit.

    If you decide to implement your own OData-enabled controllers using our product and you need assistance with some implementation issues, do not hesitate to contact us with your questions.

    Regards,
    Viktor Zhivkov
    Telerik
     
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
     
  3. DevCraft banner
  4. Paul
    Paul avatar
    17 posts
    Member since:
    May 2011

    Posted 23 Jan 2015 Link to this post

    Hi Viktor,

    I understand that the support is not complete, but I was surprised to see this particular feature fall down when I had no trouble using $expand in less complex object hierarchies.  Is there a reasonable expectation that this support might be introduced to Telerik Data Access further down the line?

    I'm familiar with the need for the detached copy in this scenario, and have worked with that API in the past.  I'm concerned about your suggestion of "manually applying" my query parameters to my LINQ query, though.  My object heirarchy is much larger than the simple example I provided, and writing custom logic to detect and expand properties for each case seems tedious.  Do you have any suggestions for a generic approach that could be applied consistently throughout my solution?

    Cheers,
    Paul
  5. Viktor Zhivkov
    Admin
    Viktor Zhivkov avatar
    291 posts

    Posted 28 Jan 2015 Link to this post

    Hello Paul,

    You can extract the $expand hints from your Request object and then build a FetchStrategy using the string-based LoadWith() method. Then you can use the same fetch strategy to perform the detaching.

    Another possibility that you may want to check to see if it will get rid of the NullReferenceException is to add default constructors to your entity types and in it initialize any collection properties and make sure to link to the base constructor (as in the case of Person and Teacher) to enable proper initialization.

    Regards,
    Viktor Zhivkov
    Telerik
     
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
     
Back to Top