AspNetCoreOData icon indicating copy to clipboard operation
AspNetCoreOData copied to clipboard

Nested $expand writes wrong SQL when PageSize is present on [EnableQuery]

Open rwb196884 opened this issue 4 years ago • 2 comments
trafficstars

I have Customer and ImageData entities and a link table CustomerImage that models a many-to-may relationship. Therefore I can use an $expand inside an $expand to get the images for a customer. For example:

/odata/customer?$top=3&$expand=CustomerImages($expand=ImageData)

This works when the controller action has the attribute [EnableQuery] but not [EnableQuery(PageSize = 100)]. It appears that different SQL is produced which doesn't even parse in the latter case.

EF model:

    public class DbContextCustomers : DbContext
    {
        public DbContextCustomers(DbContextOptions<DbContextCustomers> options) : base(options) { }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            // Configuration.
            modelBuilder.Entity<CustomerImage>().HasKey(z => new { z.CustomerID, z.ImageDataID });
            modelBuilder.Entity<CustomerImage>().HasOne(z => z.ImageData);

            modelBuilder.Entity<Customer>().HasMany<CustomerImage>(z => z.CustomerImages);
        }

        public DbSet<Customer> Customer { get; set; }
        public DbSet<ImageData> ImageData { get; set; }

    }

    public class Customer
    {
        [Key]
        public virtual int CustomerID { get; set; }
        public virtual int Number { get; set; }
        public virtual string Surname { get; set; }
        public virtual IEnumerable<CustomerImage> CustomerImages { get; set; }
    }

    public class ImageData
    {
        [Key]
        public virtual int ImageDataID { get; set; }
        public virtual DateTime ImageDateTime { get; set; }
        public virtual int PropertyID { get; set; }
        public virtual int? UserID { get; set; }

        // Will come out base64 encoded.
        [Column("ImageData")]// CS0542: member names cannot be the same as their enclosing type.
        public virtual byte[] Image { get; set; }
    }

    public class CustomerImage
    {
        [Key]
        public virtual int CustomerID { get; set; }

        [Key]
        public virtual int ImageDataID { get; set; }

        public virtual ImageData ImageData { get; set; }
    }

Controller:

    [Authorize(Roles = Role.RightToBeForgottenCreate)]
    [Route("odata/[controller]")]
    public class CustomerController : ControllerBase
    {
        private readonly ILogger<CustomerController> _logger;
        private readonly DbContextCustomers _DbContextCustomers;

        public CustomerController(
            ILogger<CustomerController> logger,
             DbContextCustomers dbContextCustomers
           )
        {
            _logger = logger;
            _DbContextCustomers = dbContextCustomers;
        }

        [EnableQuery] // Adding (PageSize = 100) makes expanding ImageData break.
        [HttpGet]
        public IQueryable<Customer> Get()
        {
            return _DbContextCustomers.Customer;
        }
    }

The invald query is:

SELECT [t0].[Number], [t0].[Surname], [t0].[CustomerID], [t2].[ModelID], [t2].[CustomerID], [t2].[ImageDataID], [t2].[ImageType], [t2].[UseInstanceForProperties], [t2].[Name], [t2].[ModelID0], [t2].[ImageDataID0], [t2].[ImageData], [t2].[ImageDateTime], [t2].[PropertyID], [t2].[UserID], [t2].[IsNull]
FROM (
    SELECT TOP(@__TypedProperty_6) [t].[Number], [t].[Surname], [t].[CustomerID]
    FROM (
        SELECT TOP(@__TypedProperty_5) [c].[CustomerID], [c].[Number], [c].[Surname]
        FROM [Customer] AS [c]
        ORDER BY [c].[CustomerID]
    ) AS [t]
    ORDER BY [t].[CustomerID]
) AS [t0]
OUTER APPLY (
    SELECT @__TypedProperty_3 AS [ModelID], [t].[CustomerID], [t].[ImageDataID], [t].[ImageType], CAST(1 AS bit) AS [UseInstanceForProperties], N'ImageData' AS [Name], @__TypedProperty_4 AS [ModelID0], [i].[ImageDataID] AS [ImageDataID0], [i].[ImageData], [i].[ImageDateTime], [i].[PropertyID], [i].[UserID], CAST(0 AS bit) AS [IsNull]
    FROM (
        SELECT TOP(@__TypedProperty_2) [c0].[CustomerID], [c0].[ImageDataID], [c0].[ImageType]
        FROM [CustomerImage] AS [c0]
        WHERE ([t0].[CustomerID] = [c0].[CustomerID]) AND ([c0].[ImageType] = @__TypedProperty_1)
        ORDER BY [c0].[CustomerID], [c0].[ImageDataID]
    ) AS [t1]
    INNER JOIN [ImageData] AS [i] ON [t].[ImageDataID] = [i].[ImageDataID]
) AS [t2]
ORDER BY [t0].[CustomerID], [t2].[CustomerID], [t2].[ImageDataID], [t2].[ImageDataID0]

rwb196884 avatar Jan 05 '21 18:01 rwb196884

I am experiencing the same issue

omnilogix avatar May 21 '21 15:05 omnilogix

OData only generates expression trees, check if the expression tree is correct by doing it on a static list with the same data shape. if so it's an ef issue.

Gigabyte0x1337 avatar Apr 15 '22 11:04 Gigabyte0x1337