将匿名类型转换为新的C#7元组类型

lmc*_*iro 30 linq-to-entities tuples anonymous-types linq-to-nhibernate c#-7.0

C#的新版本就在那里,有了新的功能元组类型:

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);
}
Run Code Online (Sandbox Code Playgroud)

有没有办法将我的匿名类型对象obj转换为我想要返回的元组而没有按属性映射属性(假设属性的名称匹配)?

上下文在ORM中,我的SomeType对象有很多其他属性,它被映射到包含大量列的表.我想做一个只带来ID和NAME的查询,所以我需要将匿名类型转换为元组,或者我需要ORM Linq Provider知道如何理解元组并将属性相关列放在SQL select子句中.

Dav*_*d L 20

简短的回答是否定的,在C#7的当前形式中,没有框架内的方式来逐字实现您的目标,因为您想要完成:

  • LINQ到实体
  • 映射到列的子集
  • 通过直接映射到C#7元组,通过属性映射从自定义或匿名类型到C#7元组避免属性.

因为Query<SomeType>暴露了一个IQueryable,所以必须对表达式树进行任何类型的投影.Select(x => new {}).

有一个开放的roslyn问题用于添加此支持,但它尚不存在.

因此,在添加此支持之前,您可以手动将匿名类型映射到元组,或者返回整个记录并将结果直接映射到元组以避免两个映射,但这显然效率低下.


虽然由于缺乏支持以及无法在.Select()投影中使用参数化构造函数,这个限制目前被纳入Linq-to-Entities ,但Linq-to-NHibernate和Linq-to-Sql都允许以创建的形式进行攻击System.Tuple.Select()投影中使用new ,然后使用.ToValueTuple()扩展方法返回ValueTuple :

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();
}
Run Code Online (Sandbox Code Playgroud)

由于System.Tuple可以映射到表达式,因此您可以从表中返回数据子集,并允许框架处理映射到C#7元组.然后,您可以使用您选择的任何命名约定来解构参数:

(int id, string customName) info = GetSomeInfo();
Console.Write(info.customName);
Run Code Online (Sandbox Code Playgroud)

  • 或者,如果已命名元组,则可以使用`System.ValueTuple`而不是`System.Tuple`. (3认同)

Pat*_*man 18

当然,通过从LINQ表达式创建元组:

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

    return obj;
}
Run Code Online (Sandbox Code Playgroud)

根据关于前C#7元组的另一个答案,您可以使用AsEnumerable()以防止EF混淆.(我对EF没什么经验,但这应该:)

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

    return obj;
}
Run Code Online (Sandbox Code Playgroud)

  • 你真的跑过这个吗?我怀疑你应该看到`CS8143表达式树可能不包含元组文字 (12认同)
  • @PatrickHofman我认为问题在于它无论如何都没有得到澄清.OP需要指定这是对象的linq还是linq到sql,因为`Query <SomeType>`是相当误导的. (6认同)
  • @DavidL:它实际上取决于`Query <SomeType>()`返回的类型,我们还不知道.如果它是`IEnumerable <SomeType>`那么这将编译,因为不需要表达式树.如果它是`IQueryable <SomeType>`那么是的,它将无法编译. (5认同)
  • 谁说EF?@AkashKava我没有在问题中看到它,也没有在标签中看到它. (4认同)
  • 我确实使用了最新的在线版本,它确实可以编译. (2认同)
  • 问题是关于将匿名类型的类型使用转换为元组.@DavidL (2认同)
  • @DavidL我很少使用EF,所以我不想在该领域建议我需要检查的任何解决方案.由于其他人似乎对此表示不满,我不得不这样做. (2认同)
  • 我认为EF是流媒体,所以它只会发送第一批.的确,效率较低.如果你在`AsEnumerable()`前面拉出`First()`可能会有所帮助.@DavidL (2认同)
  • @PatrickHofman不,实际上有三种不同类型的EF操作.在这种情况下,`.first()`是立即执行运算符,而不是流运算符.是的,你可以在`.AsEnumerable()`之前使用`.first()`,但是这会在`.Select`中突破一个元组的点,你也可以手动传播那个点的对象.这就是为什么区分linq到对象和linq到sql是如此重要...它可以大大改变答案的有效性,在这种情况下OP让我们挂起:(. (2认同)

Jef*_*ado 7

尽管表达式树当前不支持元组文字,但这并不意味着不支持该ValueTuple类型。只需显式创建它即可。

public (int id, string name) GetSomeInfo() =>
    Query<SomeType>()
        .Select(o => ValueTuple.Create(o.Id, o.Name))
        .First();
Run Code Online (Sandbox Code Playgroud)