OData / OData/AspNetCoreOData

Investigating OData's internals

Open
#266 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

question
Dominant language
C#
Stars
505
Forks
186
PR merge metrics
No merged PRs in 30d

Description

Hey guys,

I'm often in a situation where I need things resolved quickly.
To this end I'm trying to learn more about the inner workings of the OData framework in the hopes that one day I might feel confident to submit the odd pull request.

I've hit a bit of a block in my understanding of the process involved here and was wondering if I could get a hint or two to help me resolve the block.

My original problem started here (https://github.com/OData/AspNetCoreOData/issues/248).
I have since pulled down the OData code from here and added it to my solution in order to investigate the problem by stepping through it in my solution.

To cut a long story short here I post the App objects without the Roles as a single array post to a custom action with an OData query asking ?$expand=Item($expand=Roles) so i'm hoping to get all this back and that's not happening.

So my response object is an IQueryable<Result<App>> that looks like this (I've omitted other properties not relevant to the problem) ...

var result = new [] 
{
	new Result<App> {
		Success = "True",
		Message = "Created Successfully",
		Item = new App { 
			Id = 1, 
			DefaultCultureId = "", 
			Name = "TestApp1", 
			Domain = "Domain1", 
			DefaultTheme = "Default", 
			Roles: [
				new Role { Id = Guid.NewGuid(), Name = "Administrators" },
				new Role { Id = Guid.NewGuid(), Name = "Users" },
				new Role { Id = Guid.NewGuid(), Name = "Guests" }
			] 
		 }
	},
	new Result<App> {
		Success = "True",
		Message = "Created Successfully",
		Item = new App { 
			Id = 2, 
			DefaultCultureId = "", 
			Name = "TestApp2", 
			Domain = "Domain2", 
			DefaultTheme = "Default", 
			Roles: [
				new Role { Id = Guid.NewGuid(), Name = "Administrators"	},
				new Role { Id = Guid.NewGuid(),	Name = "Users" },
				new Role { Id = Guid.NewGuid(), Name = "Guests" }
			] 
		 }
	}
};

Digging through the execution as it's running reveals that the EnableAttribute when applied to my Iqueryable returns an expression tree that looks like this ...

System.Collections.Generic.List`1[Core.Objects.Result`1[Core.Objects.Entities.CMS.App]]
    .Select($it => 
		new SelectAllAndExpand`1() {
			Model = value(Microsoft.AspNetCore.OData.Query.Container.LinqParameterContainer+TypedLinqParameterContainer`1[Microsoft.OData.Edm.IEdmModel]).TypedProperty, 
			Instance = $it, 
			UseInstanceForProperties = True, 
			Container = new SingleExpandedProperty`1() 
			{
				Name = "Item", 
				Value = new SelectAllAndExpand`1() 
				{
					Model = value(Microsoft.AspNetCore.OData.Query.Container.LinqParameterContainer+TypedLinqParameterContainer`1[Microsoft.OData.Edm.IEdmModel]).TypedProperty, 
					Instance = IIF(($it == null), null, $it.Item), 
					UseInstanceForProperties = True, 
					Container = new NamedProperty`1() 
					{
						Name = "Roles", 
						Value = IIF(
							(IIF((IIF(($it == null), null, $it.Item) == null), null, IIF(($it == null), null, $it.Item).Roles) == null), null, IIF((IIF(($it == null), null, $it.Item) == null), null, IIF(($it == null), null, $it.Item).Roles)
								.Select($it => new SelectAll`1() 
								{
									Model = value(Microsoft.AspNetCore.OData.Query.Container.LinqParameterContainer+TypedLinqParameterContainer`1[Microsoft.OData.Edm.IEdmModel]).TypedProperty, 
									Instance = $it, 
									UseInstanceForProperties = True
								})
						)
					}
				}, 
				IsNull = (IIF(($it == null), null, $it.Item) == null)
			}
		}
	)

My confusion here is why did this not get translated in to something like this ...

result.Select(i => new {
	// all scalar props
	// odata props if any like etags or meta links
	Roles = i.Roles?.Select(j => {
		// all scalar props
		// odata props if any like etags or meta links
	})
})

Instead it seems that these wrappers have been introduced.
Would a direct LINQ expression tree not make the whole process easier to maintain and then from a serialization point of view we could simply do ...

await repsonse.WriteAsync(result.ToJson(serializationOptions));

I have most likely missed something here that the framework is doing but I figured I would ask as this seems like the next logical improvement to simplify the codebase in my understanding right now.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with issue #248, then trace the EnableAttribute path that produces the SelectAllAndExpand and NamedProperty expression wrappers. Compare that behavior with the proposed direct LINQ shape and review the surrounding OData serialization flow; done would require a decided, documented scope for any internal simplification.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
api, backend
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
15/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.