3

I'm using EF Core 7. I want to perform server evaluation and project to a named tuple.

I tried:

var products = await _context.Products.Select(x => (x.Id, x.Name)).ToListAsync();

Which gives:

An expression tree may not contain a tuple literal.

I could do this with a regular tuple (Tuple.Create()) or an anonymous object (new {}), but I'd like to use a named tuple, if possible.

Can this be done somehow?

lonix
  • 14,255
  • 23
  • 85
  • 176
  • You’ll have to do it after the query results return. (After tolistaaync) – Daniel A. White Feb 16 '23 at 13:31
  • @DanielA.White Yes that's what I'm doing now, converting from an anonymous type to a named tuple after the await. I was hoping there was a friendlier way to do it. Thanks for confirming. – lonix Feb 16 '23 at 13:33
  • Nope. There’s no way to extend the Expression trees, so it’s left incomplete/broken with new language features – Daniel A. White Feb 16 '23 at 13:44

2 Answers2

5

No, currently it is not possible because it is not possible to use value tuples literals in the expression trees. You can monitor following issues/discussions at the github:

If you really-really want to use value tuples the only way is to map to them after the materialization of the query, but I would argue that it is rather pointless in most cases. Something like the following:

var products = _context.Products
   .Select(x => new {x.Id, x.Name})
   .AsEnumerable()
   .Select(ac => (ac.Id, ac.Name))
   .ToList();

Though there is a trick with method indirection which can be used:

class SomeClass // local functions can't be used
{
    public static (int Id, int Name) CreateNamedValueTuple(int t1, int t2) => (t1, t2);
}

var products = await _context.Products
    .Select(x => SomeClass.CreateNamedValueTuple(x.Id, x.Name))
    .ToListAsync();

Though personally I would just used a C# 9 record feature which allows easy type creation (or C# 10 record struct).

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
1

You actually can use ValueTuple.Create() in your Select expression. EFCore will do the right thing.

var products = await _context.Products.AsNoTracking()
    .Select(x => ValueTuple.Create(x.Id, x.Name))
    .ToListAsync();

Of course, you'll have to encapsulate this in a function that returns a IEnumerable<(int Id, int Name)> as suggested by Guru Stron's answer.

Larry
  • 17,605
  • 9
  • 77
  • 106