web-dev-qa-db-fra.com

Convertir un type anonyme en un nouveau type de tuple C # 7

La nouvelle version de C # est là, avec la nouvelle fonctionnalité utile 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);
}

Existe-t-il un moyen de convertir mon objet de type anonyme obj en Tuple que je veux retourner sans mapper propriété par propriété (en supposant que les noms des propriétés correspondent)?

Le contexte est dans un ORM, mon objet SomeType a beaucoup d'autres propriétés et il est mappé sur une table avec beaucoup de colonnes. Je veux faire une requête qui n'apporte que ID et NAME, j'ai donc besoin de convertir le type anonyme en tuple, ou j'ai besoin qu'un fournisseur ORM Linq sache comment comprendre un tuple et mettre les colonnes liées aux propriétés dans la clause SQL select.

29
lmcarreiro

La réponse courte est non, dans la forme actuelle de C # 7, il n'y a aucun moyen dans le cadre d'accomplir vos objectifs textuellement, puisque vous voulez accomplir:

  • Linq-to-entity
  • Mappage à un sous-ensemble de colonnes
  • Éviter le mappage de propriété par propriété d'un type personnalisé ou anonyme à un tuple C # 7 en mappant directement à un tuple C # 7.

Étant donné que Query<SomeType> Expose un IQueryable, toute sorte de projection doit être effectuée sur une arborescence d'expressions .Select(x => new {}).

Il y a un open roslyn issue pour l'ajout de ce support, mais il n'existe pas encore.

Par conséquent, jusqu'à ce que cette prise en charge soit ajoutée, vous pouvez soit mapper manuellement d'un type anonyme à un tuple, soit renvoyer l'enregistrement entier et mapper le résultat directement à un tuple pour éviter deux mappages, mais cela est évidemment inefficace.


Bien que cette restriction soit actuellement intégrée à Linq-to-Entities en raison d'un manque de prise en charge et de l'impossibilité d'utiliser des constructeurs paramétrés dans une projection .Select(), Linq-to-NHibernate et Linq-to-Sql permettent un hack sous forme de création d'un nouveau System.Tuple dans la projection .Select(), puis retour d'un ValueTuple avec la méthode d'extension . ToValueTuple () :

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

Étant donné que System.Tuple peut être mappé à une expression, vous pouvez renvoyer un sous-ensemble de données de votre table et autoriser le framework à gérer le mappage vers votre tuple C # 7. Vous pouvez ensuite déconstruire les arguments avec n'importe quelle convention de dénomination que vous choisissez:

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

Bien sûr, en créant le Tuple à partir de votre expression LINQ:

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

    return obj;
}

Selon à une autre réponse concernant les tuples pré-C # 7, vous pouvez utiliser AsEnumerable() pour empêcher EF de mélanger les choses. (Je n'ai pas beaucoup d'expérience avec EF, mais cela devrait faire l'affaire :)

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

    return obj;
}
17
Patrick Hofman

Bien que les littéraux de tuple ne soient pas actuellement pris en charge dans les arborescences d'expressions, cela ne signifie pas que le type ValueTuple ne l'est pas. Il suffit de le créer explicitement.

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

Remarque pour quiconque utilise une version antérieure de .NET: si vous utilisez une version antérieure de .NET supérieure à 4.7.2 ou .NET Core, vous devez utiliser Nuget Package Manager pour installer System.ValueTuple dans votre projet.

Ensuite, voici un exemple d'obtention d'un tuple à partir d'une requête Linq vers 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()

Cela a fonctionné pour moi, cependant, après l'enregistrement, j'ai eu un échec de construction ... continuez à lire.

Pour encore plus de plaisir et de jeux, j'ai malheureusement eu une version antérieure de C # sur mon serveur de build pour une raison quelconque. J'ai donc dû revenir en arrière car il ne reconnaissait pas le nouveau format de tuple sur la ligne .select (o => (o.record1, o.record2)) (en particulier qu'il s'agirait d'un tuple en raison de la parenthèse autour de o. record1 et o.record2). Donc, j'ai dû revenir en arrière et en quelque sorte finagle un peu plus:

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;
1
JakeJ
    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();
    }

Cette méthode vous pouvez nommer votre élément Tuple dans une sélection linq

0
Jackdon Wang