37

The new version of C# is there, with the useful new feature Tuple Types:

public IQueryable<T> Query<T>();

public (int id, string name) GetSomeInfo() {
    var obj = Query<SomeType>()
        .Select(o => new {
            id = o.Id,
            name = o.Name,
        })
        .First();

    return (id: obj.id, name: obj.name);
}

Is there a way to convert my anonymous type object obj to the tuple that I want to return without mapping property by property (assuming that the names of the properties match)?

The context is in a ORM, my SomeType object has a lot of other properties and it is mapped to a table with lot of columns. I wanna do a query that brings just ID and NAME, so I need to convert the anonymous type into a tuple, or I need that an ORM Linq Provider know how to understand a tuple and put the properties related columns in the SQL select clause.

lmcarreiro
  • 5,312
  • 7
  • 36
  • 63
  • I think you can just do `return (obj.id, obj.name);` since you have the names in the function signature, but I don't have C# 7 right now to test it. – juharr Apr 13 '17 at 19:20
  • 4
    Wait, are you sure your anonymous class is `new { id => o.Id, name => o.Name }` and not `new { id = o.Id, name = o.Name }` – juharr Apr 13 '17 at 19:21

5 Answers5

26

Of course, by creating the tuple from your LINQ expression:

public (int id, string name) GetSomeInfo() {
    var obj = Query<SomeType>()
        .Select(o => (o.Id,o.Name))
        .First();

    return obj;
}

According to another answer regarding pre-C# 7 tuples, you can use AsEnumerable() to prevent EF to mix things up. (I have not much experience with EF, but this should do:)

public (int id, string name) GetSomeInfo() {
    var obj = Query<SomeType>()
        .AsEnumerable()
        .Select(o => (o.Id,o.Name))
        .First();

    return obj;
}
Community
  • 1
  • 1
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • 18
    Did you actually run this? I suspect you should see `CS8143 An expression tree may not contain a tuple literal.` – David L Apr 13 '17 at 19:22
  • 2
    I did using the latest online build, it does compile. – Patrick Hofman Apr 13 '17 at 19:23
  • I doubt EF supports Tuples, Tuples aren't reference types and members aren't properties. It may compile, but it may not run. – Akash Kava Apr 13 '17 at 19:23
  • 4
    Who said EF? @AkashKava I didn't read it in the question, nor in the tags. – Patrick Hofman Apr 13 '17 at 19:24
  • 6
    @PatrickHofman I think the issue is that it hasn't been clarified either way. The OP needs to specify if this is linq to objects or linq to sql since the `Query` is rather misleading. – David L Apr 13 '17 at 19:26
  • 2
    The question is about converting type use of an anonymous type to tuples. @DavidL – Patrick Hofman Apr 13 '17 at 19:27
  • Tuples aren't reference types? – Clay Apr 13 '17 at 19:30
  • No, these are not @Clay. These are structs. – Patrick Hofman Apr 13 '17 at 19:30
  • @Clay not C#7 tuples. – David L Apr 13 '17 at 19:30
  • @PatrickHofman and I completely understand what you are saying and I did not downvote your answer. However, that said, there's a 50/50 chance that this answer simply will not work if the OP failed to understand the implications of their tagging choices. At the very least you should call the distinction out in your answer. – David L Apr 13 '17 at 19:31
  • 2
    @DavidL I rarely use EF, so I don't want to suggest any solution in that field which I need to check. Since others seem to downvote for that, I have to unfortunately. – Patrick Hofman Apr 13 '17 at 19:32
  • @Ðаn Although I like the question you pose, I think it is totally different from the question here. I don't read OP wants to use reflection or nifty stuff to create the tuple. OP just doesn't know how to construct one from an expression. – Patrick Hofman Apr 13 '17 at 19:41
  • @PatrickHofman I can't get this to compile with the latest Linqpad build (5.22.03) which supports C#7 tuples natively without a NuGet package. What online compiler tool are you using? I still suspect a `.Select()` cannot deconstruct a ValueTuple and the online tool is mis-compiling. – David L Apr 13 '17 at 19:42
  • @DavidL `(id: obj.Id)`, etc. instead of `Id`, or not creating the anonymous type at all. – Patrick Hofman Apr 13 '17 at 19:43
  • @DavidL [the source looks okay](https://tryroslyn.azurewebsites.net/#K4Zwlgdg5gBAygTxAFwKYFsDcBYAUKSWRFDAOgBlIBHHXPAB2ACMAbMAYxnZYEMQQYAYRgBvPDAkxGrDjABuAezAATGAFkAFAEpR4yQF88DZm04bIyGCoA0MFACdCMCD3SodAcVTI4CtwEkIADMFbV1cSXkeexgFJgArGABeZ1QAd3DIrMkVZJgARms9bMiXNzyAIiYKooiS/VpimHtvYHsIGHNlAC5YhNIbZ1dUXrj40jL3WkM6XGlTTosrZVsHJ0nPb18A4IV8sLE6iTlovsSUiHTMksjclMKmm8nK6tqbhqMj5tb2zrGBlZnCbDLTTT6ReaycwQSyDNbQIZuTY+PyoQIhABMByaJxiYzyAFEIMA3PYeKxUKQAEo8aCoDQABls+S0pDgqBYqHYyA0AA9kgA+TqFGBVCpaUHgyQtZBtDr/ABiYHsKG0YNw+iAA=) – Patrick Hofman Apr 13 '17 at 19:44
  • @PatrickHofman manually "spreading" the tuple properties with strong naming isn't working, nor is name inference. – David L Apr 13 '17 at 19:44
  • @PatrickHofman none of those linked examples are compatible with what you're doing in a `.Select()`. It isn't a compatible example. – David L Apr 13 '17 at 19:45
  • 5
    @DavidL: It really depends on the type returned by `Query()` and we don't know that yet. If it's `IEnumerable` then this will compile because no expression trees are required. If it's `IQueryable` then yes, it will fail to compile. – Jon Skeet Apr 13 '17 at 19:49
  • @JonSkeet oh, right, of course...I was scratching my head on that for an embarrassingly long time. Thank you :) – David L Apr 13 '17 at 19:50
  • @JonSkeet Won't the second one compile, as was suggested in the referenced post from Marc? – Patrick Hofman Apr 13 '17 at 19:51
  • @PatrickHofman yes, the second will, but it will also materialize the entire dataset into memory if it is linq-to-sql, which would most likely not be what the OP wants, since they only want one record, regardless of the source. – David L Apr 13 '17 at 19:51
  • 2
    I thought EF was streaming, so it would just send the first batch over. Indeed, less efficient. It might help if you would pull the `First()` in front of the `AsEnumerable()`. @DavidL – Patrick Hofman Apr 13 '17 at 19:52
  • 2
    @PatrickHofman no, there are actually three different types of EF operations. In this case, `.First()` is an immediate execution operator, not a streaming operator. And yes, you could use `.First()` before `.AsEnumerable()`, but that would defeat the point of projecting a tuple in `.Select` and you might as well manually spread the object that point. That's why distinguishing between linq to objects and linq to sql is so important...it can dramatically change the effectiveness of the answer and in this case the OP left us hanging :(. – David L Apr 13 '17 at 19:53
  • @DavidL Sorry, I posted the question before I left my work... I've already edited the question. Thanks. – lmcarreiro Apr 15 '17 at 15:02
  • @Jaider in the context of an Entity Framework query that is projecting sql? – David L Dec 11 '18 at 00:09
  • @DavidL Absolutely! you can verify it with `SQL Server Profiler`. – Jaider Dec 12 '18 at 19:14
  • @Jaider specifically what version of the library? That is probably a newer feature. – David L Dec 12 '18 at 19:23
25

The short answer is no, in the current form of C#7 there is no in-framework way to accomplish your goals verbatim, since you want to accomplish:

  • Linq-to-entities
  • Mapping to a subset of columns
  • Avoiding property by property mapping from a custom or anonymous type to a C#7 tuple by mapping directly to a C#7 tuple.

Because Query<SomeType> exposes an IQueryable, any sort of projection must be made to an expression tree .Select(x => new {}).

There is an open roslyn issue for adding this support, but it doesn't exist yet.

As a result, until this support is added, you can either manually map from an anonymous type to a tuple, or return the entire record and map the result to a tuple directly to avoid two mappings, but this is obviously inefficient.


While this restriction is currently baked into Linq-to-Entities due to a lack of support and the inability to use parametered constructors in a .Select() projection, both Linq-to-NHibernate and Linq-to-Sql allow for a hack in the form of creating a new System.Tuple in the .Select() projection, and then returning a ValueTuple with the .ToValueTuple() extension method:

public IQueryable<T> Query<T>();

public (int id, string name) GetSomeInfo() {
    var obj = Query<SomeType>()
        .Select(o => new System.Tuple<int, string>(o.Id, o.Name))
        .First();

    return obj.ToValueTuple();
}

Since System.Tuple can be mapped to an expression, you can return a subset of data from your table and allow the framework to handle mapping to your C#7 tuple. You can then deconstruct the arguments with any naming convention you choose:

(int id, string customName) info = GetSomeInfo();
Console.Write(info.customName);
David L
  • 32,885
  • 8
  • 62
  • 93
  • 5
    Or you can use `System.ValueTuple` instead of `System.Tuple` if you have named your tuples. – Babak Dec 17 '17 at 08:18
13

While tuple literals are not currently supported in expression trees, it doesn't mean the ValueTuple type isn't. Just create it explicitly.

public (int id, string name) GetSomeInfo() =>
    Query<SomeType>()
        .Select(o => ValueTuple.Create(o.Id, o.Name))
        .First();
Jeff Mercado
  • 129,526
  • 32
  • 251
  • 272
  • 1
    ..which does compile, but fail at runtime if the implementation of the `IQueryable` does not know about `ValueTuple`, such as EF6 – M.Stramm Jan 15 '19 at 19:13
3

Note for anyone on a lower version of .NET: If you are on a lower version of .NET than 4.7.2 or .NET Core, you should use Nuget Package Manager to install System.ValueTuple to your project.

Then, here's an example of getting a tuple from a Linq to SQL query:

var myListOfTuples = (from record1 in myTable.Query()
                     join record2 in myTable2.Query() on record1.Id = record2.someForeignKey
                     select new {record1, record2}).AsEnumerable()
                     .select(o => (o.record1, o.record2))
                     .ToList()

That ran for me, however, after check-in, I got a build failure...read on.

For even more fun and games, I unfortunately had an earlier version of C# on my build server for some reason. So I had to go back because it didn't recognize the new tuple format on the .select(o => (o.record1, o.record2)) line (specifically that it would be a tuple because of the parenthesis around o.record1 and o.record2). So, I had to go back and kind of finagle it a bit more:

var myListOfAnonymousObjects = (from record1 in myTable.Query()
                     join record2 in myTable2.Query() on record1.Id = record2.someForeignKey
                     select new {record1, record2}).ToList()

var myTuples = new List<Tuple<Record1sClass, Record2sClass>>();
foreach (var pair in myListOfAnonymousObjects)
{
    myTuples.Add(pair.record1, pair.record2);
}
return myTuples;
JakeJ
  • 2,361
  • 5
  • 23
  • 35
  • Thank you! Lot's of answers wouldn't (seem) to work for me because I wanted to be able to join tables. This works perfect for me! – Caleb W. Apr 16 '20 at 00:58
-2
    public IList<(int DictionaryId, string CompanyId)> All(string DictionaryType)
    {
        var result = testEntities.dictionary.Where(p => p.Catalog == DictionaryType).ToList();
        var resultTuple = result.Select(p => (DictionaryId: p.ID, CompanyId: p.CompanyId));
        return resultTuple.ToList();
    }

This method you can named your tuple item in a linq select

Jackdon Wang
  • 369
  • 2
  • 6