OData / WebApi

OData Web API: A server library built upon ODataLib and WebApi
https://docs.microsoft.com/odata
Other
856 stars 473 forks source link

LimitResults null reference exception while expanding null valued relations #1499

Open jpiquot opened 6 years ago

jpiquot commented 6 years ago

Versions :

Webapi : latest from master branch .Net Core : 2.1.1 .Net EF Core : 2.1.1

NullReferenceException is thown when calling this OData request :

http://localhost:5000/odata/ProcurementTypes/?$expand=Warehouse($select=Name),InventoryItem($select=Name)&$count=true&$skip=0&$top=12

Method : ODataQueryOptions/LimitResults

        internal static IQueryable LimitResults(IQueryable queryable, int limit, out bool resultsLimited)
        {
            MethodInfo genericMethod = _limitResultsGenericMethod.MakeGenericMethod(queryable.ElementType);
            object[] args = new object[] { queryable, limit, null };
            IQueryable results = genericMethod.Invoke(null, args) as IQueryable;
            resultsLimited = (bool)args[2];
            return results;
        }

Arguments : queryable.ElementType :

Microsoft.AspNet.OData.Query.Expressions.SelectExpandBinder+SelectAllAndExpand`1[[Procurement.ProcurementType, Procurement.Abstractions, Version=1.3.0.0, Culture=neutral, PublicKeyToken=null]], Microsoft.AspNetCore.OData, Version=7.0.0.20625, Culture=neutral, PublicKeyToken=31bf3856ad364e35

limit = 1000

queryable =

.Call System.Linq.Queryable.Select( .Call System.Linq.Queryable.Take( .Call System.Linq.Queryable.Skip( .Call System.Linq.Queryable.OrderBy( .Constant<Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1[Procurement.ProcurementType]>(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1[Procurement.ProcurementType]), '(.Lambda #Lambda1<System.Func2[Procurement.ProcurementType,System.Int32]>)), .Constant<Microsoft.AspNet.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer1[System.Int32]>(Microsoft.AspNet.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer1[System.Int32]).TypedProperty) , .Constant<Microsoft.AspNet.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer1[System.Int32]>(Microsoft.AspNet.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer1[System.Int32]).TypedProperty) , '(.Lambda #Lambda2<System.Func2[Procurement.ProcurementType,Microsoft.AspNet.OData.Query.Expressions.SelectExpandBinder+SelectAllAndExpand`1[Procurement.ProcurementType]]>))

.Lambda #Lambda1<System.Func`2[Procurement.ProcurementType,System.Int32]>(Procurement.ProcurementType $$it) { $$it.Id }

.Lambda #Lambda2<System.Func2[Procurement.ProcurementType,Microsoft.AspNet.OData.Query.Expressions.SelectExpandBinder+SelectAllAndExpand1[Procurement.ProcurementType]]>(Procurement.ProcurementType $var1) { .New Microsoft.AspNet.OData.Query.Expressions.SelectExpandBinder+SelectAllAndExpand1[Procurement.ProcurementType](){ ModelID = "0609044c-81d8-4b4a-b0e4-b9a0ce39e90f", Instance = $var1, UseInstanceForProperties = True, Container = .New Microsoft.AspNet.OData.Query.Expressions.PropertyContainer+SingleExpandedPropertyWithNext01[Microsoft.AspNet.OData.Query.Expressions.SelectExpandBinder+SelectSomeAndInheritance1[WarehouseManagement.Warehouse]]() { Name = "Warehouse", Value = .New Microsoft.AspNet.OData.Query.Expressions.SelectExpandBinder+SelectSomeAndInheritance1[WarehouseManagement.Warehouse]() { ModelID = "0609044c-81d8-4b4a-b0e4-b9a0ce39e90f", Instance = $var1.Warehouse, Container = .New Microsoft.AspNet.OData.Query.Expressions.PropertyContainer+NamedPropertyWithNext01[System.String](){ Name = "Name", Value = ($var1.Warehouse .As GlobalAddressBook.Organization).Name, Next0 = .New Microsoft.AspNet.OData.Query.Expressions.PropertyContainer+AutoSelectedNamedProperty1[System.Nullable1[System.Int32]]() { Name = "Id", Value = (System.Nullable1[System.Int32])($var1.Warehouse .As GlobalAddressBook.Organization).Id } } }, Next0 = .New Microsoft.AspNet.OData.Query.Expressions.PropertyContainer+SingleExpandedProperty1[Microsoft.AspNet.OData.Query.Expressions.SelectExpandBinder+SelectSome1[Inventory.InventoryItem]]() { Name = "InventoryItem", Value = .New Microsoft.AspNet.OData.Query.Expressions.SelectExpandBinder+SelectSome1[Inventory.InventoryItem](){ ModelID = "0609044c-81d8-4b4a-b0e4-b9a0ce39e90f", Instance = $var1.InventoryItem, Container = .New Microsoft.AspNet.OData.Query.Expressions.PropertyContainer+NamedPropertyWithNext11[System.String](){ Name = "Name", Value = ($var1.InventoryItem).Name, Next0 = .New Microsoft.AspNet.OData.Query.Expressions.PropertyContainer+AutoSelectedNamedProperty1[System.Nullable1[System.Int32]]() { Name = "Id", Value = (System.Nullable1[System.Int32])($var1.InventoryItem).Id }, Next1 = .New Microsoft.AspNet.OData.Query.Expressions.PropertyContainer+AutoSelectedNamedProperty1[System.Byte[]](){ Name = "RowVersion", Value = ($var1.InventoryItem).RowVersion } } }, IsNull = (System.Nullable1[System.Int32])($var1.InventoryItem).Id == null }, IsNull = (System.Nullable1[System.Int32])($var1.Warehouse .As GlobalAddressBook.Organization).Id == null } } }

Stack trace :


   at lambda_method(Closure , QueryContext , TransparentIdentifier`2 )
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.ProjectionShaper.TypedProjectionShaper`3.Shape(QueryContext queryContext, ValueBuffer& valueBuffer)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.BufferlessMoveNext(DbContext _, Boolean buffer)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.MoveNext()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider._TrackEntities[TOut,TIn](IEnumerable`1 results, QueryContext queryContext, IList`1 entityTrackingInfos, IList`1 entityAccessors)+MoveNext()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
   at System.Collections.Generic.List`1.AddEnumerable(IEnumerable`1 enumerable)
   at Microsoft.AspNet.OData.Query.TruncatedCollection`1..ctor(IQueryable`1 source, Int32 pageSize) in C:\Dev\WEBCSCORE\Framework.Net\OData\src\Microsoft.AspNet.OData.Shared\Query\TruncatedCollectionOfT.cs:line 41
   at Microsoft.AspNet.OData.Query.ODataQueryOptions.LimitResults[T](IQueryable`1 queryable, Int32 limit, Boolean& resultsLimited) in C:\Dev\WEBCSCORE\Framework.Net\OData\src\Microsoft.AspNet.OData.Shared\Query\ODataQueryOptions.cs:line 685
jpiquot commented 6 years ago

The procurement type entity class is :


using Administration;
using Framework.Model;
using GlobalAddressBook;
using Inventory;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using WarehouseManagement;

namespace Procurement
{
    /// <summary>
    /// The procurement type
    /// <para>Type de demande d'approvisionnement</para>
    /// </summary>
    /// <remarks>
    /// Defines also defauts for the warehouse procurement header.
    /// <para>Le type d'approvisionnement défini aussi les paramètres par défaut de l'entête de la demande d'approvisionnement.</para>
    /// </remarks>
    public class ProcurementType : DataEntity
    {
        /// <summary>
        /// Required hierarchy node identifier.
        /// </summary>
        /// <remarks>If defined, only items in the defined hierarchy node can be added to the warehouse procurement</remarks>
        public int? HierarchyNodeId { get; set; }

        /// <summary>
        /// Required inventory item identifier.
        /// </summary>
        /// <remarks>
        /// If defined, only the defined item can be added to the warehouse procurement. For exemple, we want to define a procurement of an item for
        /// many wharehouses.
        /// </remarks>
        public int? InventoryItemId { get; set; }

        /// <summary>
        /// Required source site identifier.
        /// </summary>
        /// <remarks>If defined, only items available on this site can be added to the warehouse procurement</remarks>
        public int? SourceSiteId { get; set; }

        /// <summary>
        /// Required destination warehouse identifier.
        /// </summary>
        /// <remarks>If defined, only procurements for this warehouse can be added</remarks>
        public int? WarehouseId { get; set; }

        #region Relations

        /// <summary>
        /// Required hierarchy node.
        /// </summary>
        /// <remarks>If defined, only items in the defined hierarchy node can be added to the warehouse procurement</remarks>
        [ForeignKey(nameof(HierarchyNodeId))]
        public HierarchyNode HierarchyNode { get; set; }

        /// <summary>
        /// Required inventory item.
        /// </summary>
        /// <remarks>If defined, only the defined item can be added. For exemple, we want to define a procurement of an item for many wharehouses.</remarks>
        [ForeignKey(nameof(InventoryItemId))]
        public InventoryItem InventoryItem { get; set; }

        /// <summary>
        /// Procurements list
        /// <para>Liste de demandes d'approvisionnement</para>
        /// </summary>
        [InverseProperty(nameof(WarehouseProcurement.ProcurementType))]
        public ICollection<WarehouseProcurement> Procurements { get; set; }

        /// <summary>
        /// Required source site.
        /// </summary>
        /// <remarks>If defined, only items available on this site can be added to the warehouse procurement</remarks>
        [ForeignKey(nameof(SourceSiteId))]
        public Site SourceSite { get; set; }

        /// <summary>
        /// Required destination warehouse.
        /// </summary>
        /// <remarks>If defined, only procurements for this warehouse can be added to the warehouse procurement</remarks>
        [ForeignKey(nameof(WarehouseId))]
        public Warehouse Warehouse { get; set; }

        #endregion Relations
    }
}
jpiquot commented 6 years ago

If i don't do expand select, everything works :

http://localhost:5000/odata/ProcurementTypes/?$count=true&$skip=0&$top=3&$expand=Warehouse,InventoryItem

{
    "@odata.context": "http://localhost:5000/odata/$metadata#ProcurementTypes",
    "@odata.count": 6,
    "value": [
        {
            "@odata.etag": "W/\"YmluYXJ5J0FBQUFBQUFBRlRjPSc=\"",
            "RowVersion": "AAAAAAAAFTc=",
            "Id": 1,
            "HierarchyNodeId": 3,
            "InventoryItemId": null,
            "SourceSiteId": null,
            "WarehouseId": null,
            "Alias": null,
            "Code": "LIVREA",
            "Description": "Réassort de livres sur la plateforme",
            "Name": "Réassortiments livres",
            "ShortName": "Réassort Liv.",
            "CreatedById": "jpiquot",
            "CreatedDateTime": "2018-06-25T07:14:23.4372837+02:00",
            "ModifiedById": null,
            "ModifiedDateTime": null,
            "Warehouse": null,
            "InventoryItem": null
        },
        {
            "@odata.etag": "W/\"YmluYXJ5J0FBQUFBQUFBRlRnPSc=\"",
            "RowVersion": "AAAAAAAAFTg=",
            "Id": 2,
            "HierarchyNodeId": 3,
            "InventoryItemId": null,
            "SourceSiteId": null,
            "WarehouseId": null,
            "Alias": null,
            "Code": "LIVNPTF",
            "Description": "Approvisionnement des nouveautés livres sur la plateforme",
            "Name": "Approvisionnement nouveautés livres",
            "ShortName": "Nouv. Liv. PTF",
            "CreatedById": "jpiquot",
            "CreatedDateTime": "2018-06-25T07:14:23.4478773+02:00",
            "ModifiedById": null,
            "ModifiedDateTime": null,
            "Warehouse": null,
            "InventoryItem": null
        },
        {
            "@odata.etag": "W/\"YmluYXJ5J0FBQUFBQUFBRlRrPSc=\"",
            "RowVersion": "AAAAAAAAFTk=",
            "Id": 3,
            "HierarchyNodeId": 3,
            "InventoryItemId": null,
            "SourceSiteId": null,
            "WarehouseId": null,
            "Alias": null,
            "Code": "LIVNPDV",
            "Description": "Envoi des nouveautés livres dans les points de vente",
            "Name": "Nouveauté livres PDV",
            "ShortName": "Nouv. Liv. PDV",
            "CreatedById": "jpiquot",
            "CreatedDateTime": "2018-06-25T07:14:23.4479304+02:00",
            "ModifiedById": null,
            "ModifiedDateTime": null,
            "Warehouse": null,
            "InventoryItem": null
        }
    ]
}
xuzhg commented 6 years ago

@jpiquot I did a quick test, it works fine at my side:

image

Would you please share me a repro to dig more?

jpiquot commented 6 years ago

If I do this request : http://localhost:5000/odata/WarehouseProcurements/?$expand=Warehouse&$top=1

It is OK : {"@odata.context":"http://localhost:5000/odata/$metadata#WarehouseProcurements","value":[{"@odata.etag":"W/\"YmluYXJ5J0FBQUFBQUFBaVpjPSc=\"","RowVersion":"AAAAAAAAiZc=","Id":1,"Cancelled":false,"HierarchyNodeId":null,"InventoryItemId":null,"ProcurementTypeId":1,"SendManually":false,"SourceSiteId":null,"Status":"New","WarehouseId":null,"Alias":null,"Code":"WP0013","Description":null,"Name":"R\u00e9assort Liv. Candice","ShortName":"R\u00e9assort Liv.","CreatedById":"jpiquot","CreatedDateTime":"2018-07-04T15:41:56.7946241+02:00","ModifiedById":null,"ModifiedDateTime":null,"Warehouse":null}]}

If I add the Name field in the select clause of the expanded entity, I will have an error 500. http://localhost:5000/odata/WarehouseProcurements/?$expand=Warehouse($select=Name)&$top=1

Maybe it works for you as you have one of the records in the result set with a non null value. Could you try with all null?

jpiquot commented 6 years ago

This is a EF Model issue in my application. The issue can be closed.

luixodev commented 4 years ago

Same here, EF Model issue. A field that cannot be null in my model but in DB was save like null