Skip to content

Inconsistent anonymous record instantiation on IQueryable  #15648

@lucasteles

Description

@lucasteles

When projecting an anonymous record on IQueryable the generated tree contains a Delegate.Invoke for the anonymous type constructor, this causes some parsers/drivers to be unable to understand the operation (eg. Entity Framework)

This looks to happen when accessing deep members and having long record member names.

Repro steps

Provide the steps required to reproduce the problem:

open System
open System.Linq

type Person = { Name: string; Id : int }
type Wrapper = { Person: Person }

let data = [
    { Person = { Name = "One"; Id = 1 } }
    { Person = { Name = "Two"; Id = 2 } }
    { Person = { Name = "Three"; Id = 3 } } 
  ]

let queryWithInvoke = 
    data
      .AsQueryable()
      .Select(fun x -> {| Other = {| Name = x.Person.Name; Id = x.Person.Id |} |})
      
// short label names do not generate an invoke call
let queryWithoutInvoke = 
    data
      .AsQueryable()
      .Select(fun x -> {| Other = {| A = x.Person.Name; B = x.Person.Id |} |})

printfn "%A" queryWithInvoke.Expression;
printfn "------"
printfn "%A" queryWithoutInvoke.Expression

Outputs:

[{ Person = { Name = "One"
             Id = 9a093c1d-9a3c-4366-806e-7c32b4388a1d }
  Value = 0 }; { Person = { Name = "Two"
             Id = ded2fd37-69cd-4d56-bc05-69e539264301 }
  Value = 1 }; { Person = { Name = "Three"
             Id = 798c98fb-a772-4911-84cc-63101b369cb9 }
  Value = 2 }].Select(x => new <>f__AnonymousType1021199106`1(Name => new <>f__AnonymousType3987781292`2(x.Person.Id, Name).Invoke(x.Person.Name)))
------
[{ Person = { Name = "One"
             Id = 9a093c1d-9a3c-4366-806e-7c32b4388a1d }
  Value = 0 }; { Person = { Name = "Two"
             Id = ded2fd37-69cd-4d56-bc05-69e539264301 }
  Value = 1 }; { Person = { Name = "Three"
             Id = 798c98fb-a772-4911-84cc-63101b369cb9 }
  Value = 2 }].Select(x => new <>f__AnonymousType1021199106`1(new <>f__AnonymousType2589797714`2(x.Person.Name, x.Person.Id)))

The problem is:

new <>f__AnonymousType1021199106`1(
    Name => new <>f__AnonymousType3987781292`2(
        x.Person.Id, Name
)
.Invoke(x.Person.Name)) // <- here

The first case generates a plain constructor, and the second injects an IIFE for Person.Name which causes inconsistency in query parsing

from: fsharp/fslang-suggestions#1249

Expected behavior

No difference between the expressions

Actual behavior

Delegate invocation inside the expression

Known workarounds
Use small record labels

Related information

  • Windows 11 / MacOs / Arch Linux
  • .NET6
  • Editing Tools (any)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area-QueriesQuery expressions and library implementationBugImpact-Low(Internal MS Team use only) Describes an issue with limited impact on existing code.

    Type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions