【问题标题】:Convert anonymous type to new C# 7 tuple type将匿名类型转换为新的 C# 7 元组类型
【发布时间】:2017-09-10 00:52:50
【问题描述】:

新版本的 C# 已经出现,具有有用的新功能 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);
}

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

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

【问题讨论】:

  • 我认为你可以做return (obj.id, obj.name);,因为你在函数签名中有名字,但我现在没有 C# 7 来测试它。
  • 等等,你确定你的匿名类是new { id =&gt; o.Id, name =&gt; o.Name }而不是new { id = o.Id, name = o.Name }

标签: linq-to-entities tuples anonymous-types linq-to-nhibernate c#-7.0


【解决方案1】:

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

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

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

有一个open roslyn issue 用于添加此支持,但尚不存在。

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


虽然由于缺乏支持以及无法在 .Select() 投影中使用参数化构造函数,此限制目前已纳入 Linq-to-Entities,但 Linq-to-NHibernate 和 Linq-to-Sql 都允许在.Select() 投影中创建一个新的System.Tuple,然后使用.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();
}

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

(int id, string customName) info = GetSomeInfo();
Console.Write(info.customName);

【讨论】:

  • 或者如果你已经命名了你的元组,你可以使用System.ValueTuple而不是System.Tuple
【解决方案2】:

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

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

    return obj;
}

根据 to another answer 关于 pre-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;
}

【讨论】:

  • 你真的运行过这个吗?我怀疑你应该看到CS8143 An expression tree may not contain a tuple literal.
  • 我使用了最新的在线版本,它可以编译。
  • 谁说 EF? @AkashKava 我没有在问题中阅读它,也没有在标签中阅读它。
  • @PatrickHofman 我认为问题在于它没有得到澄清。 OP 需要指定这是 linq to objects 还是 linq to sql,因为 Query&lt;SomeType&gt; 具有误导性。
  • @DavidL:这真的取决于Query&lt;SomeType&gt;() 返回的类型,我们还不知道。如果它是IEnumerable&lt;SomeType&gt;,那么这将编译,因为不需要表达式树。如果是IQueryable&lt;SomeType&gt; 那么是的,它将无法编译。
【解决方案3】:

虽然表达式树目前不支持元组文字,但这并不意味着 ValueTuple 类型不支持。直接创建即可。

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

【讨论】:

  • ..如果IQueryable的实现不知道ValueTuple,例如EF6,它会编译,但在运行时会失败
【解决方案4】:

对于低版本 .NET 的任何人的注意事项: 如果您使用的 .NET 版本低于 4.7.2 或 .NET Core,则应使用 Nuget 包管理器将 System.ValueTuple 安装到您的项目中。

然后,这是一个从 Linq to SQL 查询中获取元组的示例:

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()

这是为我运行的,但是,在签入后,我遇到了构建失败...请继续阅读。

为了获得更多乐趣和游戏,不幸的是,出于某种原因,我的构建服务器上有一个早期版本的 C#。所以我不得不回去,因为它无法识别 .select(o => (o.record1, o.record2)) 行上的新元组格式(特别是因为 o 周围的括号,它会是一个元组。记录 1 和 o.record2)。所以,我不得不回去再多花点心思:

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;

【讨论】:

  • 谢谢!很多答案(似乎)对我不起作用,因为我希望能够加入表格。这对我来说很完美!
【解决方案5】:
    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();
    }

此方法可以在 linq 选择中命名您的元组项

【讨论】:

  • 有了这个代码,你就失去了IQueryable的好处
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-19
  • 1970-01-01
  • 2010-11-27
  • 2012-05-01
  • 2020-04-18
  • 2012-01-20
相关资源
最近更新 更多