web-dev-qa-db-fra.com

Entity Framework / Linq EXpression conversion de chaîne en entier

J'ai une expression comme ça:

var values = Enumerable.Range(1,2);

return message => message.Properties.Any(
    p => p.Key == name 
    && int.Parse(p.Value) >= values[0] 
    && int.Parse(p.Value) <= values[1]);

Cela compile très bien mais quand il frappe la base de données il lève l'exception 'LINQ to Entities does not recognize the method 'Int32 Parse(System.String)' method, and this method cannot be translated into a store expression '

Si je ne fais pas l'analyse et que les valeurs sont un string[], Je ne peux pas utiliser les opérateurs >= Et <= Sur les chaînes.

p.Value Est une chaîne contenant différentes valeurs mais dans ce cas, c'est int

Existe-t-il un moyen de demander à la base de données de faire ce genre de déclaration entre?

17
Jon

Comme d'autres l'ont souligné dans les commentaires, le fait que vous deviez analyser cette valeur devrait être un indicateur rouge que vous devez utiliser un type de données différent dans votre base de données.

Heureusement, il existe une solution de contournement en forçant la requête à être exécutée par LINQ to Objects plutôt que LINQ to Entities. Malheureusement, cela signifie potentiellement lire une grande quantité de données en mémoire

MODIFIER

Sur la base de vos autres commentaires, la valeur dans la colonne Valeur n'est pas garantie d'être un nombre. Par conséquent, vous devrez essayer de convertir la valeur en nombre, puis de gérer les choses en fonction de l'échec/du succès de cette conversion:

return message
       .Properties
       .AsEnumerable()
       .Any(p => 
            {
                var val = 0;
                if(int.TryParse(p.Value, out val))
                {
                    return p.Key == name &&
                           val >= values[0] &&
                           val <= values[1])
                }
                else
                {
                    return false;
                }
           );

MODIFIER 2

Vous pourrez peut-être vous en sortir dans la base de données. Je ne sais pas si cela fonctionnera ou non pour vous, mais essayez-le:

return message.Properties
              .Where(p => p.Key == name && SqlFunctions.IsNumeric(p.Value) > 0)
              .Any(p => Convert.ToInt32(p.Value) >= values[0] &&
                        Convert.ToInt32(p.Value) <= values[1]);
4
Justin Niessner

Autant je déteste cette réponse, la vraie réponse est que vous ne pouvez pas le faire facilement. Ce sera une vraie douleur. J'ai vu beaucoup de mauvaises réponses et beaucoup de réponses avec des gens qui disent que vous devriez simplement avoir vos champs de base de données du bon type en premier lieu, ce qui n'est pas utile.

Votre question est similaire à cette question sur MSDN .

Il existe plusieurs possibilités selon le type d'EF que vous utilisez.

1. Vous utilisez des fichiers EDMX.

(Ne pas coder en premier ou code inversé en premier).

Vous pouvez utiliser quelque chose comme ça ( à partir de cette réponse ):

[EdmFunction("PlusDomain", "ParseDouble")]
public static double ParseDouble(string stringvalue)
{
    // This method exists for use in LINQ queries,
    // as a stub that will be converted to a SQL CAST statement.
    return System.Double.Parse(stringvalue);
}

et mappez-le dans votre EDMX comme ceci:

<Function Name="ParseDouble" ReturnType="Edm.Double">
    <Parameter Name="stringvalue" Type="Edm.String" />
    <DefiningExpression>
        cast(stringvalue as Edm.Double)
    </DefiningExpression>
</Function>

2. Si vous utilisez d'abord le code dans EF> = 4.1.

Vous êtes foutus. Microsoft n'a pas jugé bon d'ajouter une telle fonction à SqlFunctions. Le mieux que vous puissiez espérer est d'ajouter une fonction SQL scalaire dans votre base de données et (peut-être) d'essayer de la mapper dans votre contexte. Microsoft n'a vu aucun intérêt à faire quelque chose comme ça dans le code en premier. Heureusement, ils n'ont pas totalement bloqué ces choses non plus. L'API Fluent est puissante.

Vous pouvez appeler les fonctions ou les procédures stockées comme ceci ( référence ):

var outParam = new SqlParameter("overHours", SqlDbType.Int);
outParam.Direction = ParameterDirection.Output;

Ou comme ça ( référence ):

var data = context.Database.ExecuteSqlCommand("dbo.sp_getNumberJobs @overHours OUT", outParam);
int numJobs = (int)outParam.Value;

Mais pour les faire réellement s'intégrer dans LINQ to Entities, vous avez besoin de quelque chose comme CodeFirstFunctions , en utilisant le EntityFramework.CodeFirstStoreFunctions package NuGet. Il mappe les fonctions SQL dans le contexte, mais il utilise une bibliothèque externe uniquement conçue pour .NET 4.5 (voir ici ).

Au lieu de cela, vous pouvez essayer de faire la même chose manuellement comme dans cette question .

La solution la plus rapide que j'ai choisie pour mes besoins consiste simplement à créer une vue avec les types convertis. Cela évite tout le problème.

15
Menace

J'ai récemment dû convertir une chaîne (représentation numérique) en valeur d'énumération dans une requête LINQ, sans matérialiser la requête. Mon énumération a utilisé des valeurs int entre 0 et 9, donc j'ai fini par faire quelque chose comme ceci:

    .Select(str => (MyEnum?)(SqlFunctions.Unicode(str) - 48))

Je sais que cela ne fournit pas une solution complète au problème, mais cela fonctionne certainement dans une situation comme la mienne. Certains trouveront peut-être cela utile.

1
Adi