web-dev-qa-db-fra.com

Appeler une méthode dans une requête Linq

Je souhaite insérer dans ma table une colonne nommée 'S' qui obtiendra une valeur de chaîne basée sur une valeur obtenue à partir d'une colonne de table.

Par exemple: for each ID (a.z) Je veux obtenir sa valeur de chaîne stockée dans une autre table. La valeur de chaîne est renvoyée par une autre méthode qui l'obtient via une requête Linq.

  • Est-il possible d'appeler une méthode depuis Linq?
  • Dois-je tout faire dans la même requête?

Voici la structure des informations dont j'ai besoin pour obtenir:

a.z est l'identifiant dans le premier carré de la table n ° 1; à partir de cet identifiant, je reçois un autre identifiant dans la table n ° 2 et à partir de là, je peux obtenir la valeur de chaîne que je dois afficher dans la colonne 'S'.
enter image description here

var q = (from a in v.A join b in v.B
    on a.i equals b.j
    where a.k == "aaa" && a.h == 0
    select new {T = a.i, S = someMethod(a.z).ToString()})
    return q;

La ligne S = someMethod(a.z).ToString() à l'origine de l'erreur suivante: 

Impossible de convertir l'objet de type 'System.Data.Linq.SqlClient.SqlColumn' pour taper 'System.Data.Linq.SqlClient.SqlMethodCall'.

30
user990635

Vous devez exécuter votre appel de méthode dans le contexte Linq-to-Objects car, du côté de la base de données, cet appel de méthode n’aura aucun sens - vous pouvez le faire à l’aide de AsEnumerable() - le reste de la requête sera ensuite évalué en tant que collection en mémoire à l'aide de Linq-to-Objects. peut utiliser les appels de méthode comme prévu:

var q = (from a in v.A join b in v.B
        on a.i equals b.j
        where a.k == "aaa" && a.h == 0
        select new {T = a.i, Z = a.z })
        .AsEnumerable()
        .Select(x => new { T = x.T, S = someMethod(x.Z).ToString() })
50
BrokenGlass

Vous voudrez le scinder en deux déclarations. Renvoyez les résultats de la requête (ce qui touchera la base de données), puis énumérez les résultats une seconde fois dans une étape distincte pour transformer la traduction en nouvelle liste d'objets. Cette seconde "requête" n'ira pas dans la base de données, vous pourrez donc utiliser la fonction someMethod() à l'intérieur.

Linq-to-Entities est un peu étrange, car il rend le passage à l'interrogation de la base de données depuis C # extrêmement transparent: vous devez toujours vous rappeler que "Ce C # va être traduit en SQL." Et en conséquence, vous devez vous demander: "Tout ce C # peut-il être réellement exécuté en tant que SQL?" Si ce n'est pas le cas - si vous appelez someMethod() à l'intérieur de celle-ci - votre requête posera problème. Et la solution habituelle est de la scinder.

(L'autre réponse de @BrokenGlass, en utilisant .AsEnumerable(), est fondamentalement une autre façon de faire cela.)

10
Ken Smith

C'est une vieille question, mais je ne vois personne ne mentionne un "hack", qui permet d'appeler des méthodes pendant select sans réitérer. L'idée est d'utiliser constructeur et dans constructeur, vous pouvez appeler ce que vous voulez (au moins, cela fonctionne très bien dans LINQ avec NHibernate, vous n'êtes pas sûr de LINQ2SQL ni de EF, mais je suppose que ce devrait être pareil) ... ci-dessous, j'ai le code source pour un programme de référence, il semble que l'approche répétée dans mon cas soit environ deux fois plus lente que celle du constructeur et je suppose que cela n'est pas étonnant - ma logique métier était minimale, donc des choses comme l'itération et l'allocation de mémoire sont importantes.

J'aurais aussi souhaité qu'il y ait un meilleur moyen de dire, ceci ou cela ne devrait pas être essayé d'exécuter sur la base de données,

// Here are the results of selecting sum of 1 million ints on my machine:
// Name    Iterations      Percent    
// reiterate       294     53.3575317604356%      
// constructor     551     100%

public class A
{
    public A()
    {            
    }

    public A(int b, int c)
    {
        Result = Sum(b, c);
    }

    public int Result { get; set; }

    public static int Sum(int source1, int source2)
    {
        return source1 + source2;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var range = Enumerable.Range(1, 1000000).ToList();

        BenchmarkIt.Benchmark.This("reiterate", () =>
            {
                var tst = range
                    .Select(x => new { b = x, c = x })
                    .AsEnumerable()
                    .Select(x => new A
                    {
                        Result = A.Sum(x.b, x.c)
                    })
                    .ToList();
            })
            .Against.This("constructor", () =>
            {
                var tst = range
                    .Select(x => new A(x, x))
                    .ToList();
            })
            .For(60)
            .Seconds()
            .PrintComparison();

        Console.ReadKey();
    }
}
0
Giedrius