Thursday, August 3, 2017

Why is Entity Framework generating the following nested SQL for Azure Mobile Services Table Controllers

Leave a Comment

I'm trying to get to the bottom of an entity Framework issue when using it with a TableController

I've created the following setup.

  1. The basic TodoItem example provided with a new Mobile Web Api which leverages EntityFramework, TableController & the default EntityDomainManager

    public class TodoItemController : TableController<TodoItem> {     protected override void Initialize(HttpControllerContext controllerContext)     {         base.Initialize(controllerContext);         context = new MobileServiceContext();         context.Database.Log += LogToDebug;         DomainManager = new EntityDomainManager<TodoItem>(context, Request);     }      public IQueryable<TodoItem> GetAllTodoItems()     {         var q = Query();         return q;     } 
  2. A vanilla Web API 2 controller.

    public class TodoItemsWebController : ApiController {      private MobileServiceContext db = new MobileServiceContext();     public TodoItemsWebController()     {         db.Database.Log += LogToDebug;     }      public IQueryable<TodoItem> GetTodoItems()     {         return db.TodoItems;     } 

I've gone through the tablecontroller code with a fine tooth comb, digging into the Query method, which is just proxying the call via the DomainManager to add in the Where(_ => !_.IsDeleted) modfiication to the IQueryable

Yet the two queries produce VERY different SQL.

For the regular Web API Controller you get the following SQL.

SELECT      [Extent1].[Id] AS [Id],      [Extent1].[Version] AS [Version],      [Extent1].[CreatedAt] AS [CreatedAt],      [Extent1].[UpdatedAt] AS [UpdatedAt],      [Extent1].[Deleted] AS [Deleted],      [Extent1].[Text] AS [Text],      [Extent1].[Complete] AS [Complete]     FROM [dbo].[TodoItems] AS [Extent1] 

But for the TableController, you get the following chunk of SQL which has a *Magic* Guid in the middle of it, and results in a Nested SQL statement. The performance of this goes to complete garbage when you start dealing with any of the ODATAv3 queries like $top, $skip, $filter and $expand.

SELECT TOP (51)      [Project1].[C1] AS [C1],      [Project1].[C2] AS [C2],      [Project1].[C3] AS [C3],      [Project1].[Complete] AS [Complete],      [Project1].[C4] AS [C4],      [Project1].[Text] AS [Text],      [Project1].[C5] AS [C5],      [Project1].[Deleted] AS [Deleted],      [Project1].[C6] AS [C6],      [Project1].[UpdatedAt] AS [UpdatedAt],      [Project1].[C7] AS [C7],      [Project1].[CreatedAt] AS [CreatedAt],      [Project1].[C8] AS [C8],      [Project1].[Version] AS [Version],      [Project1].[C9] AS [C9],      [Project1].[Id] AS [Id]     FROM ( SELECT          [Extent1].[Id] AS [Id],          [Extent1].[Version] AS [Version],          [Extent1].[CreatedAt] AS [CreatedAt],          [Extent1].[UpdatedAt] AS [UpdatedAt],          [Extent1].[Deleted] AS [Deleted],          [Extent1].[Text] AS [Text],          [Extent1].[Complete] AS [Complete],          1 AS [C1],          N'804f84c6-7576-488a-af10-d7a6402da3bb' AS [C2],          N'Complete' AS [C3],          N'Text' AS [C4],          N'Deleted' AS [C5],          N'UpdatedAt' AS [C6],          N'CreatedAt' AS [C7],          N'Version' AS [C8],          N'Id' AS [C9]         FROM [dbo].[TodoItems] AS [Extent1]     )  AS [Project1]     ORDER BY [Project1].[Id] ASC 

You can see the results of both queries here. https://pastebin.com/tSACq6eg

So my questions are:

  • Why is the TableController generating the SQL in this way?

  • What is the *magic* guid in the middle of the query? (it will stay the same until I stop and restart the app so I don't know if it's session, client or DB context specific)

  • Where exactly in the pipeline is the TableController making these Modifications to the IQueryable? I assume it's done through some middleware step, or an on executed attribute later in the request after the Query() method is called, but I cannot for the life of me find it.

2 Answers

Answers 1

According to your description, I did some research and found that Azure Mobile Server SDK uses the following code line under TableControllerConfigProvider.cs for adding additional query related filters for the same actions with the QueryableAttribute for enabling a controller action to support OData query parameters.

controllerSettings.Services.Add(typeof(IFilterProvider), new TableFilterProvider()); 

Note: the additional filters would be executed after your action has been executed and return the IQueryable.

You could check EnableQueryAttribute.cs and found that OnActionExecuted would call the ExecuteQuery method and eventually call ODataQueryOptions.ApplyTo for applying OData query options ($filter, $orderby, $top, $skip, and $inlinecount, etc.) to the given IQueryable.

Per my understanding, the Nested SQL statement is generated by OData component. After invoked ODataQueryOptions.ApplyTo, your IQueryable has been modified and the related sql statement has been modified too. I did some test in my regular Web API Controller as follows, you could refer to it:

Request:

Get http://localhost:58971/api/todoitem?$top=2&$select=Text,Id,Version 

Before applying OData query options:

enter image description here

After applied OData query options:

enter image description here

Answers 2

Your one of the tables being synced between the backend and client because if that your are getting 2nd sql.

Read more here : https://documentation.devexpress.com/wpf/17927/Common-Concepts/Scaffolding-Wizard/Tutorials/Building-Outlook-Inspired-and-Hybrid-UI-Applications/Lesson-3-Customize-Layout-of-the-Collection-Views

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment