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

Convert anonymous type to new C# 7 tuple type

新版本的 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);
}

有没有一种方法可以将我的匿名类型对象 obj 转换为我想要 return 的元组,而无需通过 属性 映射 属性(假设属性名称匹配)?

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

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

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

    return obj;
}

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

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

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

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

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

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


虽然由于缺乏支持以及无法在 .Select() 投影中使用参数化构造函数,此限制目前已纳入 Linq-to-Entities,但 Linq-to-NHibernate 和 Linq-to- Sql 允许以在 .Select() 投影中创建新 System.Tuple 的形式进行黑客攻击,然后使用 .ToValueTuple() 扩展方法 returning 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 可以映射到表达式,您可以 return 来自 table 的数据子集,并允许框架处理到 C#7 元组的映射。然后,您可以使用您选择的任何命名约定来解构参数:

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

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

public (int id, string name) GetSomeInfo() =>
    Query<SomeType>()
        .Select(o => ValueTuple.Create(o.Id, o.Name))
        .First();
    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 中命名您的元组项 select

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

然后,这是从 Linq 到 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()

对我来说 运行,但是,在 check-in 之后,我遇到了构建失败...请继续阅读。

为了获得更多乐趣和游戏,不幸的是,出于某种原因,我的构建服务器上安装了早期版本的 C#。所以我不得不回去,因为它无法识别 .select(o => (o.record1, o.record2)) 行上的新元组格式(特别是它会是一个元组,因为o.record1 和 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;