2016-07-18 9 views
6

Ho la seguente tabella in SQL Server:Corporatura Espressione Albero convertibile a SQL valida in modo dinamico che può confrontare stringa con doppie

Tipi di prodotto

  • Nome: nvarchar(100)
  • Valore: nvarchar(200)

Questo è mappato tramite Entity Framework nella mia classe:

public class ProductAttribute 
{ 
    public string Name {get;set;} 
    public string Value {get;set;} 
} 

Alcune righe di ProductAttributes avere la seguente forma:

  • {Name: "RAM", Value: "8 GB"}, {Name: "Cache", Value: "3000KB"}

ho bisogno di costruire dinamicamente un ExpressionTree che è convertibile a SQL che può fa il seguente:

  • Se il valore inizia con un numero seguito o meno da una stringa alfanumerica, estrarre il numero e confrontarlo con un dato valore

    double value = ...; 
    
    Expression<Func<ProductAttribute, bool>> expression = p => 
    { 
          Regex regex = new Regex(@"\d+"); 
          Match match = regex.Match(value); 
    
          if (match.Success && match.Index == 0) 
          { 
           matchExpression = value.Contains(_parserConfig.TokenSeparator) ? 
            value.Substring(0, value.IndexOf(_parserConfig.TokenSeparator)) : 
            value; 
    
           string comparand = match.Value; 
           if(double.Parse(comparand)>value) 
            return true; 
          } 
    
          return false; 
    } 
    

La cosa veramente brutta è che ho bisogno di costrutto questo albero espressione dinamicamente.

Finora ho gestito questa (questo considera il valore come decimale non come una stringa, quindi non prova nemmeno a fare tutta la roba regex):

private Expression GenerateAnyNumericPredicate(
     Type type, 
     string valueProperty, 
     string keyValue, 
     double value) { 
     ParameterExpression param = Expression.Parameter(type, "s"); 
     MemberExpression source = Expression.Property(param, valueProperty); 
     ConstantExpression targetValue = GetConstantExpression(value, value.GetType()); 
     BinaryExpression comparisonExpression = Expression.GreaterThan(source, targetValue); 

     return Expression.Lambda(comparisonExpression, param); 
    } 

EDIT: Con l'aiuto fornito di seguito, questo funziona:

Expression<Func<ProductSpecification, bool>> expo = ps=> ps.Value.Substring(0, (SqlFunctions.PatIndex("%[^0-9]%", ps.Value + ".") ?? 0) - 1) == "1000"; 

Ma ho anche bisogno di un cast di raddoppiare e poi un confronto numerico che è:

Expression<Func<ProductSpecification, bool>> expo = ps=> double.Parse(ps.Value.Substring(0, (SqlFunctions.PatIndex("%[^0-9]%", ps.Value + ".") ?? 0) - 1)) > 1000; 

Ovviamente questo non è convertibile in SQL: double.Parse().

Come è possibile creare il cast in modo che possa essere analizzato in SQL dalla mia espressione?

+0

Quale sarebbe l'SQL che cosa vuoi generare? –

+0

Francamente non lo so, ma qualcosa di equivalente a questo. –

+0

Se i dati sono un lotto, è possibile che si verifichi un problema di prestazioni se questo tipo di query viene eseguito ancora e ancora, probabilmente INDICE non può essere utilizzato. Quindi un modo migliore è di avere una colonna numerica separata con valore numerico, è possibile aggiornare quella colonna al momento dell'inserimento/aggiornamento. – sallushan

risposta

2

Penso che Yacoub Massad abbia ragione a chiedersi come dovrebbe essere l'SQL. Se non c'è modo di scrivere SQL che esegue la query, come può esserci un albero di espressioni che converte nell'SQL richiesto?

Il problema principale è che la regex non è supportata in modo nativo da SQL Server. È possibile importare una funzione CLR nel database e usarla in una UDF, ma non è il modo più semplice per farlo funzionare con EF.

Quindi, ancora una volta, iniziare con l'imaging di SQL che farebbe il lavoro.

Ora ho trovato this piccolo gioiello che estrae la parte numerica (a sinistra) da una stringa:

select left(@str, patindex('%[^0-9]%', @str+'.') - 1) 

Questa sarebbe tornato "3000" da "3000KB".

Fortunatamente, possiamo usare SqlFunctions.PatIndex di riprodurre questo in una dichiarazione LINQ:

from pa in context.ProductAttributes 
select pa.Value.Substring(0, (SqlFunctions.PatIndex("%[^0-9]%", pa.Value + ".") ?? 0) - 1) 

Il che, ovviamente, tornare 8 e 3000 dai vostri esempi.

Ora la parte più difficile è fatto, è possibile utilizzare questo risultato per applicare un predicato a questa parte numerica:

from pa in context.ProductAttributes 
let numPart = pa.Value.Substring(0, (SqlFunctions.PatIndex("%[^0-9]%", pa.Value + ".") ?? 0) - 1) 
where numPart .... // go ahead 

vedrai che per ogni volta che si utilizza numPart nella dichiarazione LINQ, tutto questo PatIndex roba viene ripetuta nell'istruzione SQL (anche se lo si avvolgere in una sottoquery). Sfortunatamente, è così che funziona SQL. Non è possibile memorizzare un risultato temporaneo in-statement. Bene, le specifiche del linguaggio hanno più di 40 anni, non male affatto.

+0

Ho modificato la domanda un po '. La tua risposta funziona quasi, ma ho anche bisogno di una stringa di cast-> double + comparison. Sai come posso ottenere questo? –

+0

Siamo spiacenti, non è supportato in LINQ alle entità. La conversione del tipo è una grande mancanza in EF. –

0

Ho intenzione di andare con impossibile.

Come si estrae in modo affidabile il numero utilizzando SQL? Non è possibile utilizzare regex. La cosa migliore che puoi fare è cercare una sorta di separatore tra il numero e il testo possibili, che i tuoi dati di test non hanno costantemente: "RAM" ha uno spazio in "8 GB", ma "Cache" non in "300kb" ".

1

NON farlo. Motivo: il confronto con i doppi suggerisce che si può dire: RAM> 4, ma 4 cosa? se si memorizzano 2000 KB, allora sarà vero, ma se si memorizzano 8 MB, non lo sarà, che è ovviamente falso. Invece: memorizza un valore normalizzato per il doppio nel db accanto al tuo campo e scegli questo. Se disponi già dei dati, esegui una migrazione migliore.

Problemi correlati