Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problems in selecting columns in related entites with expand #92

Closed
johanparmar opened this issue Mar 26, 2015 · 18 comments
Closed

Problems in selecting columns in related entites with expand #92

johanparmar opened this issue Mar 26, 2015 · 18 comments
Labels

Comments

@johanparmar
Copy link

Hi!
I have been trying to get simple.odata.client ODATA v4 to work with a complex datastructure where I just want to select certain columns in each table on different levels. I have been using the untyped version. The problem I'm having that it is only selecting on the top level. If I have the following structure:

public class A
{
int Id;
string Name;
string description;
List BList;
}

public class B
{
int Id;
string Name;
string description;
List CList;
}
public class C
{
int Id;
string Name;
string description;
}

        var annotations = new ODataFeedAnnotations();
        var items = oDataClient
            .For("A")
            .Expand("B/C")
            .Select("Name", "Description","B/Name", "B/C/Id", "B/C/Name")
            .FindEntriesAsync(annotations).Result;

What I'm expecting to get is an untyped object which looks like this:

A: {Name, Description, BList:{Name, CList:{Id, Name}}}

Unfortunately I'm getting the all the properties for B and C

@object
Copy link
Member

object commented Mar 26, 2015

Thank you for the issue report. I will investigate it and get back to you.

@object
Copy link
Member

object commented Apr 4, 2015

I have uploaded the version 4.3.1 with a few changes to Expand/Select logic. However I was not able to reproduce exactly your scenario. Can you try 4.3.1 and if you are still getting all fields selected I would like to check the HTTP GET calls generated by the library (and the sample code).

@johanparmar
Copy link
Author

Hi,
I updated my version of the client to the version 4.3.1. Unfortunately I'm still getting the same problem. The structure above was just made up to simplify what I'm was trying to convey. Here is how the code actually looks:

The api call:

        var annotations = new ODataFeedAnnotations();
        var items =
            oDataClient.For<ClientProductSku>()
                .Filter(x => x.ClientId == clientId)
                .Expand("Product/ProductCategory/Category")
                .Select("PartNo", "ClientId", "Product/ProductCategory/Category/Code")
                .FindEntriesAsync(annotations)
                .Result;

The http GET call:

GET /odata/ClientProductSkus?$filter=ClientId%20eq%2035&$expand=Product($expand=ProductCategory($expand=Category))&$select=PartNo,ClientId&$count=true HTTP/1.1

As you can see it does not add the select for the Category entity/resource. I'm not quite sure how the the uri should look but I tried this:

http://localhost:50216/odata/ClientProductSkus?$filter=ClientId%20eq%2035&$expand=Product($expand=ProductCategory($expand=Category))&$select=PartNo,ClientId,Product/ProductCategory/Category/Code&$count=true

Unfortunately it didn't work. I'm getting the following OData error (in the browser):
"Found a path with multiple navigation properties or a bad complex property path in a select clause. Please reword your query such that each level of select or expand only contains either TypeSegments or Properties."
.

@object
Copy link
Member

object commented Apr 7, 2015

That's strange. I checked my integration tests and I have a test for similar functionality here:
https://github.com/object/Simple.OData.Client/blob/master/Simple.OData.Client.IntegrationTests/TripPinTests.cs

[Fact]
public async Task FindPersonFlightExpandAndSelect()
{
var flight = await _client
.For<Person>()
.Key("russellwhyte")
.NavigateTo(x => x.Trips)
.Key(1003)
.NavigateTo(x => x.PlanItems)
.Key(21)
.As<Flight>()
.Expand(x => x.Airline)
.Select(x => new { x.FlightNumber, x.Airline.AirlineCode})
.FindEntryAsync();
Assert.Null(flight.From);
Assert.Null(flight.To);
Assert.Null(flight.Airline.Name);
Assert.Equal("FM", flight.Airline.AirlineCode);
}

Is it possible for you to generate and example that I might try myself? Or give me a temporary access to your OData service?

@johanparmar
Copy link
Author

Unfortunately I can not give you access to the customer's OData service. Fyi the OData service is done with web api 2, entity framework with an existing data model.

I also tried to the typed syntax to see if that made any difference but with same result as before

Select(x => new {x.ClientId, x.PartNo, CategoryCode = x.Product.ProductCategory.Select(y=>y.Category.Code)})

I'll try to put together an example later this week with an public OData service. I just hope I cant get the same issue with that?

@object
Copy link
Member

object commented Apr 8, 2015

Alternative is if you can extract a sample project (and some sample data) that I can build myself and check what's going on. Ideally a project with a test that fails.

@johanparmar
Copy link
Author

Sorry, Closed by mistake.
Thanks for your great support Vagif.

I was thinking where it might go wrong. Can it be that there is something wrong in the Odata Service which causes the client to behave like this? In that case, would it help you if I send the metadata file to you?

Otherwise the only option, as you have mentioned above, is to try to reproduce the problem in a sample project.

@johanparmar johanparmar reopened this Apr 9, 2015
@object
Copy link
Member

object commented Apr 9, 2015

Yes, I can look at the metadata document, perhaps it will uncover something.

@johanparmar
Copy link
Author

Do you have any email adress I can send it to?

@object
Copy link
Member

object commented Apr 9, 2015

vagif.abilov@gmail.com

@object
Copy link
Member

object commented Apr 14, 2015

I think the issue with expansion is fixed. It will be a part of the version 4.5 that will be out sometime next week, but you can check out its pre-release from MyGet feed:

https://www.myget.org/feed/Packages/simple-odata-client

@johanparmar
Copy link
Author

That's great new vagif!
I will surely check it out

On Tuesday, April 14, 2015, Vagif Abilov notifications@github.com wrote:

I think the issue with expansion is fixed. It will be a part of the
version 4.5 that will be out sometime next week, but you can check out its
pre-release from MyGet feed:

https://www.myget.org/feed/Packages/simple-odata-client

Reply to this email directly or view it on GitHub
#92 (comment)
.

Sent from Gmail Mobile

@object object closed this as completed Apr 20, 2015
@johanparmar
Copy link
Author

Hi Vagif, I have tested version 4.5.3 of the client with our code and we are still experiencing the same problem. We have the following code:

   public void GenerateDeltaFile(int offsetInMinutes)
    {
        var annotations = new ODataFeedAnnotations();
        var items =
            oDataClient.For<ClientProductSku>()
            .Function(FunctionNameGetCreateUpdateSkuDelta)
            .Set(new { clientId, offsetInMinutes })
                .Expand(CreateUpdateExpandTables)
                .Select(CreateUpdateSelectColumns)
                .FindEntriesAsync(annotations)
                .Result;
        using (var generator = new CreateUpdateSkuStreamingXmlFileGenerator(FileType.Delta))
        {
            generator.GenerateStartXmlDocument();
            WriteItemsToFile(items, generator);
            while (annotations.NextPageLink != null)
            {
                items =
                    oDataClient.For<ClientProductSku>()
            .Function(FunctionNameGetCreateUpdateSkuDelta)
            .Set(new { clientId, offsetInMinutes })
                        .Expand(CreateUpdateExpandTables)
                        .Select(CreateUpdateSelectColumns)
                        .FindEntriesAsync(annotations.NextPageLink, annotations)
                        .Result;
                WriteItemsToFile(items, generator);
            }
            generator.GenerateEndXmlDocument();
        }
    }


    private static readonly string[] CreateUpdateSelectColumns = {
        "PartNo", "ClientId", "ErpName", "EanCode", 
        "Product/Id","Product/ManufacturerId",
        "Product/ProductCategory/IsPrimary",
        "Product/ProductCategory/Category/Code", 
        "ClientProductSkuPriceList/CurrencyId"
    };



    private static readonly string[] CreateUpdateExpandTables = {
        "Product/ProductCategory/Category/CategorySalesArea",            
        "ClientProductSkuPriceList",
        "ClientProductSkuSalesArea",
        "Product/SupplierProductSkuClient/SupplierProductSku/SupplierProductSkuPriceList/SupplierPriceList",
        "Product/SupplierProductSkuClient/SupplierProductSku/SupplierProductSkuOnHand/Warehouse"

    };

This gives us the url:

GET/odata/ClientProductSkus/FunctionService.GetCreateUpdateSkuDelta(clientId=35,offsetInMinutes=2000)?
$expand=Product($expand=ProductCategory($expand=Category($expand=CategorySalesArea));$select=Id,ManufacturerId),
ClientProductSkuPriceList($select=CurrencyId),
ClientProductSkuSalesArea,
Product($expand=SupplierProductSkuClient($expand=SupplierProductSku($expand=SupplierProductSkuPriceList($expand=SupplierPriceList)));$select=Id,ManufacturerId),
Product($expand=SupplierProductSkuClient($expand=SupplierProductSku($expand=SupplierProductSkuOnHand($expand=Warehouse)));$select=Id,ManufacturerId)&
$select=PartNo,ClientId,ErpName,EanCode&$count=true HTTP/1.1

But it should rather be something like this:

GET/odata/ClientProductSkus/FunctionService.GetCreateUpdateSkuDelta(clientId=35,offsetInMinutes=2000)?
$expand=Product($expand=ProductCategory($expand=Category($expand=CategorySalesArea;$select=Code);$select=IsPrimary);$select=Id,ManufacturerId),
ClientProductSkuPriceList($select=CurrencyId),
ClientProductSkuSalesArea,
Product($expand=SupplierProductSkuClient($expand=SupplierProductSku($expand=SupplierProductSkuPriceList($expand=SupplierPriceList)));$select=Id,ManufacturerId),
Product($expand=SupplierProductSkuClient($expand=SupplierProductSku($expand=SupplierProductSkuOnHand($expand=Warehouse)));$select=Id,ManufacturerId)&
$select=PartNo,ClientId,ErpName,EanCode&$count=true HTTP/1.1

I hope this is sufficient info for you.

@object
Copy link
Member

object commented May 5, 2015

No it's not sufficient. I can't find in the metadata file that you earlier sent me a function definition for "FunctionNameGetCreateUpdateSkuDelta". And knowing metadata structure is crucial to find an error. Please send recent metadata document to me.

@object object reopened this May 5, 2015
@johanparmar
Copy link
Author

Hi Vagif
Have just emailed you the metadata file.

@object
Copy link
Member

object commented May 6, 2015

Please checkout version 4.5.4. I made a fix.

@object object added the bug label May 7, 2015
@johanparmar
Copy link
Author

Hi Vagif,
Sorry for the late reply. This is working fine for me now.
Thanks!

@taishmanov
Copy link

taishmanov commented Nov 14, 2018

For people who use typed approach, extend "select" method using .Net reflection.

As an example there is extension method below:
`public static class ODataClientExtensions
{
public static IBoundClient SelectDefaults(this IBoundClient source) where FT : class
{
var type = typeof(FT);
var attrType = typeof(DataMemberAttribute);

        var columns = new List<string>();
        foreach (var property in type.GetProperties())
        {
            var attribute = property.GetCustomAttributes(attrType, false).FirstOrDefault();
            if (attribute != null && attribute is DataMemberAttribute)
            {
                var colunm = (attribute as DataMemberAttribute).Name;
                if (property.PropertyType.IsSubclassOf(typeof(BaseEntity)))
                {//You can extend it recursively collecting property names. I needed only "closest" related entities.
                    foreach (var childProperty in property.PropertyType.GetProperties())
                    {
                        var childAttribute = childProperty.GetCustomAttributes(attrType, false).FirstOrDefault();
                        if (childAttribute != null && childAttribute is DataMemberAttribute)
                        {
                            var childColunm = (childAttribute as DataMemberAttribute).Name;
                            if (!childProperty.PropertyType.IsSubclassOf(typeof(BaseEntity)))
                                columns.Add($"{colunm}/{childColunm}");
                        }
                    }

                }
                else
                {
                    columns.Add(colunm);
                }
            }

        }
        return source.Select(columns);
    }
}`

Example of use:
await ApiClient.For<TimeEntry>() .Key(id) .Expand(te => te.Project) .SelectDefaults() .FindEntryAsync();

Later I will create pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants