no eager loading with expand on Data Services

9 posts, 0 answers
  1. Christian
    Christian avatar
    6 posts
    Member since:
    Apr 2014

    Posted 08 Apr 2014 Link to this post

    Hello,

    in my szenario i have 2 tables with a 1:n relationship (Customer and CustomerDetails) . I have a fluent one side association mapping on Customer like this:

    customerConfiguration.HasAssociation(x => x.CustomerDetails)
    .HasFieldName("_customerDetails")
    .ToColumn("customerId");


    my UnitOfWork Implementation in my Domain Context looks like this:

    public IQueryable Customer Customers
    {
       get { return this.GetAll<Customer>(); }
    }



    On Top i build a WCF data service. this works fine. now i call from my client a sample query:
     
    domaincontext.Customers.ToList();


    At the result i get a list of customers without CustomerDetails. Thats ok. if i add a
    expand(x=> x.CustomerDetails)

    i get a customer list with details. But when i watch with the Sql profiler on the generated statements i see one select for all Customers and for every CustomerDetail row a single select (n+1 problem). I would expect that open access interpret the expand method as an eager loading behavior.

    if I add the loadBehavior manually to the fluent mapping, it work as expecting. but i think that is not the solution because every time i execute a query on customers all details will be joined (but not transfered to the client), even without the exand method.

    So is it possible that the eager loading of customerdetails only execute if the expand method is called?

    Thank you for your assistance
    christian






  2. Christian
    Christian avatar
    6 posts
    Member since:
    Apr 2014

    Posted 09 Apr 2014 in reply to Christian Link to this post

    a little correction: I get not a single select for each detail row, but for each Customer a single select for details
  3. DevCraft banner
  4. Boyan
    Admin
    Boyan avatar
    100 posts

    Posted 11 Apr 2014 Link to this post

    Hi Christian,

    The behavior you are experiencing is a known issue on out side. Fortunately I could suggest an easy workaround using the Fetch Strategy feature of Telerik Data Access. You could create a custom method that returns all Customers along with their Details. Such method could look like this:

    [WebGet]
    public IQueryable<Customer> CustomersWithDetails()
    {
        var query = this.CurrentDataSource.Customers.Include(x => x.Details);
        return query;
    }

    Please note that in order for this approach to work you will need to include an extend statement when accessing the method. For example:
    http://localhost/FluentModelService.svc/CustomersWithDetails?$expand=Details

    Please excuse us for the inconvenience this might caused you. 

    Do not hesitate to contact us if you have any more questions or need any further assistance. 

    Regards,
    Boyan
    Telerik
     
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
     
  5. Christian
    Christian avatar
    6 posts
    Member since:
    Apr 2014

    Posted 15 Apr 2014 in reply to Boyan Link to this post

    Hi Boyan,

    thank's for your help. I understand your solution. But I think you use the WebApi DataServices? I use the Data Service like this:

    http://docs.telerik.com/data-access/developers-guide/using-web-services/data-services/developer-guide-wcfservices-data-expose-oacontext

    In front I have no WebApplication, i use a simple console host. When i tried to integrate your suggestion in my IUnityOfWork implementation i found a unexpected behavior. I define an Interface like this:

    public interface ICustomerUoW : IUnitOfWork
    {
       IQueryable<Customer> Customers { get; }
    }


    And i have Contextimplemention like this:

    public class CustomerContext : OpenAccessContext, ICustomerUoW
    {
       .....
     public IQueryable<Customer> Customers
     {
       //get { return this.GetAll<Customer>().Include(x=> x.Details); }
       //get { return null; }
       get { return  this.GetAll<Customer>(); }
     }
    }


    I doesnt matter what Queryable the property returns. Even when i return null, the Query works fine. The Debugger runs never to the Code. It is correct, that is only important to define the Interface and your Data Access Framework fetch (perhaps with reflection) the Method name and return type?

    The second think i found out is, that when fetch the Details with eager loading in the Customer mapping and i have no expand in the query, but a projection it works like expected. The Sql query doesnt fetch the Details when i have a projection. Thats ok.

    So my Question is: is it possible to force my client to define an projection? Without projection i would reject the query.

    Thank's for your help
    christian
  6. Christian
    Christian avatar
    6 posts
    Member since:
    Apr 2014

    Posted 17 Apr 2014 in reply to Christian Link to this post

    Hello,

    ok i get some findings. First of all i found out what you do in your example. This is not a WebApi Example, but a WCF Data Service operation.

    The second finding is, i found a way to block queries without projections. But i think this restriction is to heavy.

    Now i try to define an QueryInterceptor. The Interceptor should parse the query for "$expand" statements and build a expression that call all Include operations for each expand.

    I think its possible. But i dont know how i resolve the RequestUri in an QueryInterceptor. Can you give me hint? I'm hosting the OpenAccessDataService in a Console Application and not in a WebApplication.

    thanks
    Christian
  7. Boyan
    Admin
    Boyan avatar
    100 posts

    Posted 17 Apr 2014 Link to this post

    Hi Christian,

    I actually followed the same approach as described in the documentation article you linked. Please excuse me for the misunderstanding.

    The work process I proposed should be independent of the hosting option you have chosen. Once the Telerik Data Access Service Wizard finishes execution, it should have added a .svc file in your project. This file contain a class that inherits from OpenAccessDataService. This is the place where I have added the CustomersWithDetails() method. In order for this method to be accessible from a client I also added the following line in the InitializeService() of the same class:
    config.SetServiceOperationAccessRule("CustomersWithDetails", ServiceOperationRights.All);

    If you have created a Service Reference to a project and you would like to consume this method with a .Net Client, you could invoke it using the Execute method. For example:
    var baseAddress = "http://localhost:12646/EntitiesModel1Service.svc/";
    var client = new EntitiesModel1(new Uri(baseAddress));
    var result = client.Execute<Customer>(new Uri(baseAddress + "CustomersWithDetails"));

    Yes, you were right - the implementation of the OpenAccessDataService would use Reflection to discover all IQueryable properties of the context. When one is queried, the service implementation would actually invoke the GetAll<T> method of the Telerik Data Access context rather the the property itself. This is the reason why you could not change the behavior of the service by modifying the contexts' properties.

    Generally speaking, to get access to the Request URI you need to override the OnStartProcessingRequest method in your service class. Still, according to this MSDN article, query interceptors is applied to methods that must return Expression<Func<T, bool>>. This is why such solution may not be applicable in this situation. Could you please share more on your idea?

    I would recommend to use the dedicated method approach. Could you please try it and let us know how it works. 

    Should you have any further questions, please let us know.

    Regards,
    Boyan
    Telerik
     
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
     
  8. Christian
    Christian avatar
    6 posts
    Member since:
    Apr 2014

    Posted 22 Apr 2014 in reply to Boyan Link to this post

    Hello,

    yes the service method approach works fine. But for my background it is to static. The Customer with details was a example to explain my problem. But in our real world, we have multiple domains for those we create a common architecure. every domain get a command interface for the communication with other domains. Additionally every domain should get Query interface with a flat domain model especially for Queries. (On the Database we set up flat query views). OData give other domains a exellent possibility to fetch data without calling a special method for every Query.

    We try to hold our query interfaces flat but sometimes we have some relationsships. And we wont tell our domain programmers to build a special Service operation for every Relationship. Especially if a view have for example 3 or 4 possible relationsships to other views. We wont eager load for every query all relationships or build for every possible combination a service method.

    Yes, you were right. The idea to you use the query interceptors will not work, because of the expression return parameter instead of IQueryable. My idea was to read all expand requests from the Uri and call for every one an include method.

    Do you know, if you plan to fix the issue in your data service Implemenation?

    Best Regards,
    Christian
  9. Boyan
    Admin
    Boyan avatar
    100 posts

    Posted 24 Apr 2014 Link to this post

    Hello Christian,

    The issue in question is definitely going to be addressed and fixed but at this moment I cannot give you an exact time frame.

    An alternative approach to achieve the same functionality could be  to parse URI - similar to your suggestion.
    You could do that by overriding the OnStartProcessingRequest method located in your service class. Once you know which entities are part of an expand statement you could use a per context fetch strategy to load them avoiding the issue in question. Here a simple example to demonstrate the idea:

    protected override void OnStartProcessingRequest(ProcessRequestArgs args)
    {
        base.OnStartProcessingRequest(args);
     
        // At this point the URI should be parsed and it should be decided which           // entities to be loaded as well
        if (args.RequestUri.AbsoluteUri.Contains("expand"))
        {
            var fetchStrategy = new FetchStrategy();
            fetchStrategy.LoadWith<Author>("Books");
            fetchStrategy.MaxFetchDepth = 5;
            this.CurrentDataSource.FetchStrategy = fetchStrategy;
        }
    }


    Please note, this example is just for demonstrating the principle and there is still work to be done with parsing the URI

    I hope this is helpful. Please let us know if you need further assistance. 

    Regards,
    Boyan
    Telerik
     
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
     
  10. Christian
    Christian avatar
    6 posts
    Member since:
    Apr 2014

    Posted 07 May 2014 in reply to Boyan Link to this post

    Hi Boyan,

    your suggestion works well. I've build a solution to parse the URI (Inclusive recursive expands) and resolve with reflection the typ infos. Then i create a FetchStrategy object.

    Thank's for your help.

    Regards
    Christian
Back to Top
DevCraft banner