Restituzione di IEnumerable <T> vs. IQueryable <T>


1085

Qual è la differenza tra return IQueryable<T>vs IEnumerable<T>, quando uno dovrebbe essere preferito rispetto all'altro?

IQueryable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

IEnumerable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

Risposte:


1778

Sì, entrambi ti daranno l' esecuzione differita .

La differenza è che IQueryable<T>è l'interfaccia che consente a LINQ-to-SQL (LINQ.-to-any davvero) di funzionare. Pertanto, se si perfeziona ulteriormente la query su un IQueryable<T>, tale query verrà eseguita nel database, se possibile.

Nel IEnumerable<T>caso, sarà LINQ-to-object, il che significa che tutti gli oggetti corrispondenti alla query originale dovranno essere caricati nella memoria dal database.

Nel codice:

IQueryable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);

Tale codice eseguirà SQL per selezionare solo i clienti gold. Il seguente codice, invece, eseguirà la query originale nel database, quindi filtrando i clienti non gold nella memoria:

IEnumerable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);

Questa è una differenza abbastanza importante, e lavorare su IQueryable<T>può in molti casi salvarti dal restituire troppe righe dal database. Un altro primo esempio è il paging: se usi Takee Skipaccendi IQueryable, otterrai solo il numero di righe richieste; farlo su un IEnumerable<T>farà sì che tutte le tue righe vengano caricate in memoria.


32
Ottima spiegazione Ci sono situazioni in cui IEnumerable sarebbe preferibile a IQueryable?
fjxx,

8
Quindi possiamo dire che se stiamo usando IQueryable per interrogare Memory Object, allora non ci sarà alcuna differenza tra IEnumerable e IQueryable?
Tarik

11
ATTENZIONE: Sebbene IQueryable possa essere una soluzione allettante a causa dell'ottimizzazione dichiarata, non dovrebbe essere consentito oltre il repository o il livello di servizio. Questo serve a proteggere il database dall'overhead causato dallo "stacking delle espressioni LINQ".
Yorro,

48
@fjxx Sì. Se si desidera filtrare ripetutamente il risultato originale (diversi risultati finali). Farlo sull'interfaccia IQueryable comporterà diversi viaggi di andata e ritorno nel database, dove facendolo su IEnumerable eseguirà il filtraggio in memoria, rendendolo più veloce (a meno che la quantità di dati non sia ENORME)
Per Hornshøj-Schierbeck il

34
Un altro motivo per preferire IEnumerablea IQueryableè che non tutte le operazioni di LINQ sono supportate da tutti i provider LINQ. Quindi, finché sai cosa stai facendo, puoi usare IQueryableper inviare la maggior parte della query al provider LINQ (LINQ2SQL, EF, NHibernate, MongoDB ecc.). Ma se lasci che un altro codice faccia quello che vuole con il IQueryabletuo finirai per finire nei guai perché un codice client da qualche parte ha usato un'operazione non supportata. Concordo con la raccomandazione di non rilasciare IQueryables "in the wild" oltre il repository o un livello equivalente.
Avish,

302

La risposta migliore è buona ma non menziona gli alberi delle espressioni che spiegano "come" differiscono le due interfacce. Fondamentalmente, ci sono due set identici di estensioni LINQ. Where(), Sum(), Count(), FirstOrDefault(), Ecc tutti hanno due versioni: una che accetta le funzioni e uno che accetta le espressioni.

  • La IEnumerablefirma della versione è:Where(Func<Customer, bool> predicate)

  • La IQueryablefirma della versione è:Where(Expression<Func<Customer, bool>> predicate)

Probabilmente hai usato entrambi quelli senza accorgertene perché entrambi sono chiamati usando una sintassi identica:

ad esempio Where(x => x.City == "<City>")funziona su entrambi IEnumerableeIQueryable

  • Quando si utilizza Where()su una IEnumerableraccolta, il compilatore passa una funzione compilata aWhere()

  • Quando si utilizza Where()su una IQueryableraccolta, il compilatore passa un albero delle espressioni a Where(). Un albero delle espressioni è come il sistema di riflessione ma per il codice. Il compilatore converte il tuo codice in una struttura di dati che descrive cosa fa il tuo codice in un formato facilmente digeribile.

Perché preoccuparsi di questa cosa dell'albero delle espressioni? Voglio solo Where()filtrare i miei dati. Il motivo principale è che entrambi gli ORM EF e Linq2SQL possono convertire gli alberi delle espressioni direttamente in SQL, dove il codice verrà eseguito molto più velocemente.

Oh, sembra un aumento delle prestazioni gratuito, dovrei usare AsQueryable()ovunque in quel caso? No, IQueryableè utile solo se il fornitore di dati sottostante può farci qualcosa. Conversione di qualcosa di simile a un normale Listper IQueryablenon ti darà alcun beneficio.


9
IMO è meglio della risposta accettata. Tuttavia, non capisco una cosa: IQueryable non offre alcun vantaggio per oggetti normali, ok, ma è peggio in qualche modo? Perché se non dà alcun vantaggio, non è sufficiente un motivo per preferire IEnumerable, quindi l'idea di utilizzare IQueryable ovunque rimane valida.
Sergei Tachenov,

1
Sergey, IQueryable estende IEnumerable, quindi quando usi IQueryable carichi più memoria che un'istanza di IEnumerable! Quindi ecco un argomento. ( stackoverflow.com/questions/12064828/… c ++ anche se pensavo di poterlo estrapolare)
Viking

Concordo con Sergei sul fatto che questa sia la risposta migliore (sebbene la risposta accettata vada bene). Vorrei aggiungere che nella mia esperienza, IQueryablenon funzioni parse così come IEnumerablefa: per esempio, se volete sapere quali elementi di un DBSet<BookEntity>non sono in una List<BookObject>, dbSetObject.Where(e => !listObject.Any(o => o.bookEntitySource == e))genera un'eccezione: Expression of type 'BookEntity' cannot be used for parameter of type 'BookObject' of method 'Boolean Contains[BookObject] (IEnumerable[BookObject], BookObject)'. Ho dovuto aggiungere .ToList()dopo dbSetObject.
Jean-David Lanz,

80

Sì, entrambi utilizzano l'esecuzione differita. Illustriamo la differenza usando il profiler di SQL Server ....

Quando eseguiamo il seguente codice:

MarketDevEntities db = new MarketDevEntities();

IEnumerable<WebLog> first = db.WebLogs;
var second = first.Where(c => c.DurationSeconds > 10);
var third = second.Where(c => c.WebLogID > 100);
var result = third.Where(c => c.EmailAddress.Length > 11);

Console.Write(result.First().UserName);

Nel profiler di SQL Server troviamo un comando uguale a:

"SELECT * FROM [dbo].[WebLog]"

Ci vogliono circa 90 secondi per eseguire quel blocco di codice su una tabella WebLog che ha 1 milione di record.

Quindi, tutti i record della tabella vengono caricati in memoria come oggetti e quindi con ciascuno. Dove () sarà un altro filtro in memoria contro questi oggetti.

Quando utilizziamo IQueryableanziché IEnumerablenell'esempio sopra (seconda riga):

Nel profiler di SQL Server troviamo un comando uguale a:

"SELECT TOP 1 * FROM [dbo].[WebLog] WHERE [DurationSeconds] > 10 AND [WebLogID] > 100 AND LEN([EmailAddress]) > 11"

Ci vogliono circa quattro secondi per eseguire questo blocco di codice usando IQueryable.

IQueryable ha una proprietà chiamata Expressionche memorizza un'espressione ad albero che inizia a essere creata quando abbiamo usato il resultnostro esempio (che si chiama esecuzione differita), e alla fine questa espressione verrà convertita in una query SQL per essere eseguita sul motore di database.


5
Questo mi insegna quando eseguo il casting su IEnumerable, il sottostante IQueryable perde il suo metodo di estensione IQueryable.
Yiping

56

Entrambi ti daranno l'esecuzione differita, sì.

Quanto a quale è preferito rispetto all'altro, dipende da quale sia l'origine dati sottostante.

La restituzione di un IEnumerableimporrà automaticamente al runtime di utilizzare LINQ to Objects per eseguire una query sulla raccolta.

Restituire un IQueryable(che implementa IEnumerable, a proposito) fornisce la funzionalità extra per tradurre la tua query in qualcosa che potrebbe funzionare meglio sull'origine sottostante (LINQ to SQL, LINQ to XML, ecc.).


30

In termini generali, consiglierei quanto segue:

  • Restituisci IQueryable<T>se desideri abilitare lo sviluppatore utilizzando il tuo metodo per perfezionare la query restituita prima di eseguire.

  • Restituire IEnumerablese si desidera trasportare un set di oggetti da enumerare.

Immagina una IQueryablecosa del genere: una "query" per i dati (che puoi perfezionare se vuoi). An IEnumerableè un insieme di oggetti (che è già stato ricevuto o è stato creato) su cui è possibile enumerare.


2
"Can enumerate", "not" can IEnumerable. "
Casey,

28

Molto è stato detto in precedenza, ma tornando alle origini, in un modo più tecnico:

  1. IEnumerable è una raccolta di oggetti in memoria che è possibile enumerare , una sequenza in memoria che consente di scorrere attraverso (rende più semplice il foreachciclo all'interno , sebbene sia possibile utilizzarlo IEnumeratorsolo). Risiedono nella memoria così com'è.
  2. IQueryable è un albero di espressioni che verrà tradotto in qualcos'altro ad un certo punto con la capacità di enumerare il risultato finale . Immagino che questo sia ciò che confonde la maggior parte delle persone.

Ovviamente hanno connotazioni diverse.

IQueryablerappresenta un albero di espressioni (una query, semplicemente) che verrà tradotto in qualcos'altro dal provider di query sottostante non appena vengono chiamate API di rilascio, come le funzioni aggregate LINQ (Somma, Conteggio, ecc.) o ToList [Array, Dictionary ,. ..]. E IQueryableanche gli oggetti implementano IEnumerable, in IEnumerable<T>modo che se rappresentano una query il risultato di quella query potrebbe essere ripetuto. Significa che IQueryable non deve essere solo query. Il termine giusto è che sono alberi di espressione .

Ora come vengono eseguite queste espressioni e ciò a cui si rivolgono dipende dai cosiddetti provider di query (esecutori di espressioni a cui possiamo pensare).

Nel mondo Entity Framework (ovvero il mistico fornitore di origine dati sottostante o il fornitore di query) le IQueryableespressioni vengono tradotte in query T-SQL native . Nhibernatefa cose simili con loro. Puoi scriverne uno tuo seguendo i concetti abbastanza ben descritti in LINQ: Creazione di un link Provider IQueryable , ad esempio, e potresti voler avere un'API di query personalizzata per il servizio del fornitore dell'archivio prodotti.

Quindi, fondamentalmente, gli IQueryableoggetti vengono costruiti fino a quando non li rilasciamo esplicitamente e diciamo al sistema di riscriverli in SQL o altro e inviare la catena di esecuzione per l'elaborazione successiva.

Come se l' esecuzione differita sia una LINQfunzione per contenere lo schema dell'albero delle espressioni nella memoria e inviarlo nell'esecuzione solo su richiesta, ogni volta che determinate API vengono chiamate contro la sequenza (lo stesso Count, ToList, ecc.).

L'uso corretto di entrambi dipende in larga misura dalle attività da affrontare per il caso specifico. Per il noto modello di repository, personalmente scelgo di tornare IList, cioè IEnumerablesu Liste (indicizzatori e simili). Quindi è il mio consiglio di usare IQueryablesolo all'interno di repository e IEnumerable in qualsiasi altra parte del codice. Non dire delle preoccupazioni sulla testabilità che IQueryableinterrompono e rovinano il principio di separazione delle preoccupazioni . Se si restituisce un'espressione all'interno dei repository, i consumatori possono giocare con il livello di persistenza come desiderano.

Una piccola aggiunta al disordine :) (da una discussione nei commenti)) Nessuno di loro è un oggetto in memoria dato che non sono dei veri e propri tipi, sono dei marcatori di un tipo - se vuoi andare così in profondità. Ma ha senso (ed è per questo che anche MSDN lo dice in questo modo) pensare a IEnumerables come raccolte in memoria mentre IQueryables come alberi delle espressioni. Il punto è che l'interfaccia IQueryable eredita l'interfaccia IEnumerable in modo che se rappresenta una query, i risultati di quella query possano essere enumerati. L'enumerazione determina l'esecuzione dell'albero delle espressioni associato a un oggetto IQueryable. Quindi, in effetti, non puoi davvero chiamare nessun membro IEnumerable senza avere l'oggetto in memoria. Entrerà lì se lo fai, comunque, se non è vuoto. IQueryables sono solo query, non dati.


3
Il commento che IEnumerables sono sempre in memoria non è necessariamente vero. L'interfaccia IQueryable implementa l'interfaccia IEnumerable. Per questo motivo, è possibile passare un IQueryable non elaborato che rappresenta una query LINQ-to-SQL direttamente in una vista che prevede un IEnumerable! Potresti essere sorpreso di scoprire che il tuo contesto di dati è scaduto o che si verificano problemi con MARS (più set di risultati attivi).

quindi, in effetti, non puoi davvero chiamare nessun membro IEnumerable senza avere l'oggetto in memoria. Entrerà lì se lo fai, comunque, se non è vuoto. IQueryables sono solo query, non dati. Ma vedo davvero il tuo punto. Aggiungerò un commento su questo.
Arman McHitarian

@AlexanderPritchard nessuno di loro è un oggetto in memoria dato che non sono dei veri e propri tipi, sono dei marcatori di un tipo - se vuoi andare così in profondità. Ma ha senso (ed è per questo che anche MSDN lo dice in questo modo) pensare a IEnumerables come raccolte in memoria mentre IQueryables come alberi delle espressioni. Il punto è che l'interfaccia IQueryable eredita l'interfaccia IEnumerable in modo che se rappresenta una query, i risultati di quella query possano essere enumerati. L'enumerazione determina l'esecuzione dell'albero delle espressioni associato a un oggetto IQueryable.
Arman McHitarian

24

In generale, si desidera preservare il tipo statico originale della query fino a quando non è importante.

Per questo motivo, puoi definire la tua variabile come 'var' invece di uno IQueryable<>o IEnumerable<>e saprai che non stai cambiando il tipo.

Se inizi con un IQueryable<>, in genere vuoi tenerlo come un IQueryable<>fino a quando non ci sono motivi validi per cambiarlo. Il motivo è che si desidera fornire al processore di query quante più informazioni possibili. Ad esempio, se utilizzerai solo 10 risultati (che hai chiamato Take(10)), vuoi che SQL Server lo sappia in modo da poter ottimizzare i suoi piani di query e inviarti solo i dati che utilizzerai.

Un motivo convincente per cambiare il tipo da IQueryable<>a IEnumerable<>potrebbe essere che stai chiamando alcune funzioni di estensione che l'implementazione IQueryable<>nel tuo oggetto particolare non è in grado di gestire o gestire in modo inefficiente. In tal caso, potresti voler convertire il tipo in IEnumerable<>(assegnando a una variabile di tipo IEnumerable<>o utilizzando AsEnumerablead esempio il metodo di estensione) in modo che le funzioni di estensione che chiami finiscano per essere quelle nella Enumerableclasse anziché nella Queryableclasse.


18

Esiste un post sul blog con un breve esempio di codice sorgente su come l'uso improprio IEnumerable<T>può influire notevolmente sulle prestazioni delle query LINQ: Entity Framework: IQueryable vs. IEnumerable .

Se scaviamo più a fondo e esaminiamo le fonti, possiamo vedere che ci sono ovviamente diversi metodi di estensione che vengono eseguiti IEnumerable<T>:

// Type: System.Linq.Enumerable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Enumerable
{
    public static IEnumerable<TSource> Where<TSource>(
        this IEnumerable<TSource> source, 
        Func<TSource, bool> predicate)
    {
        return (IEnumerable<TSource>) 
            new Enumerable.WhereEnumerableIterator<TSource>(source, predicate);
    }
}

e IQueryable<T>:

// Type: System.Linq.Queryable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(
        this IQueryable<TSource> source, 
        Expression<Func<TSource, bool>> predicate)
    {
        return source.Provider.CreateQuery<TSource>(
            Expression.Call(
                null, 
                ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(
                    new Type[] { typeof(TSource) }), 
                    new Expression[] 
                        { source.Expression, Expression.Quote(predicate) }));
    }
}

Il primo restituisce un iteratore enumerabile e il secondo crea una query tramite il provider di query, specificato IQueryablenell'origine.


11

Di recente ho riscontrato un problema con IEnumerablev IQueryable. L'algoritmo utilizzato per primo ha eseguito una IQueryablequery per ottenere una serie di risultati. Questi sono stati quindi passati a un foreachciclo, con gli elementi istanziati come una classe Entity Framework (EF). Questa classe EF è stata quindi utilizzata nella fromclausola di una query Linq to Entity, causando il risultato IEnumerable.

Sono abbastanza nuovo per EF e Linq per le entità, quindi ci è voluto un po 'di tempo per capire quale fosse il collo di bottiglia. Utilizzando MiniProfiling, ho trovato la query e quindi ho convertito tutte le singole operazioni in una singola IQueryablequery Linq per le entità. Ci IEnumerablevollero 15 secondi e IQueryable0,5 secondi per l'esecuzione. C'erano tre tabelle coinvolte e, dopo aver letto questo, credo che la IEnumerablequery stesse effettivamente formando un prodotto incrociato di tre tabelle e filtrando i risultati.

Prova a usare IQueryables come regola empirica e profila il tuo lavoro per rendere misurabili le tue modifiche.


il motivo era che le espressioni IQueryable vengono convertite in un SQL nativo in EF e vengono eseguite proprio nel DB mentre gli elenchi IEnumerable sono oggetti in memoria. Vengono recuperati dal DB ad un certo punto quando chiamate funzioni aggregate come Count, Sum o qualunque A ... e successivamente operano in memoria. IQueryablegli s sono anche bloccati nella memoria una volta che hai chiamato una di quelle API ma in caso contrario, puoi passare l'espressione nello stack dei livelli e giocare con i filtri fino alla chiamata API. Il DAL ben progettato come un repository ben progettato risolverà questo tipo di problemi;)
Arman McHitarian

10

Vorrei chiarire alcune cose a causa di risposte apparentemente contrastanti (principalmente attorno a IEnumerable).

(1) IQueryableestende l' IEnumerableinterfaccia. (È possibile inviare un IQueryablea qualcosa che si aspetta IEnumerablesenza errori.)

(2) Entrambi IQueryablee IEnumerableLINQ tentano il caricamento lento durante l'iterazione sul set di risultati. (Si noti che l'implementazione può essere vista nei metodi di estensione dell'interfaccia per ciascun tipo.)

In altre parole, IEnumerablesnon sono esclusivamente "in memoria". IQueryablesnon vengono sempre eseguiti sul database. IEnumerabledeve caricare le cose in memoria (una volta recuperate, forse pigramente) perché non ha un fornitore di dati astratto. IQueryablesfare affidamento su un provider astratto (come LINQ-to-SQL), anche se questo potrebbe anche essere il provider .NET in-memory.

Esempio di caso d'uso

(a) Recupera l'elenco dei record IQueryabledal contesto EF. (Nessun record è in memoria.)

(b) Passa IQueryablea una vista il cui modello è IEnumerable. (Valido. IQueryableEstende IEnumerable.)

(c) Scorrere e accedere ai record, alle entità figlio e alle proprietà del set di dati dalla vista. (Può causare eccezioni!)

Possibili problemi

(1) I IEnumerabletentativi di caricamento lento e il contesto dei dati sono scaduti. Eccezione generata perché il provider non è più disponibile.

(2) I proxy di entità Entity Framework sono abilitati (impostazione predefinita) e si tenta di accedere a un oggetto (virtuale) correlato con un contesto di dati scaduto. Come per (1).

(3) Più set di risultati attivi (MARS). Se stai ripetendo l'iter IEnumerablein un foreach( var record in resultSet )blocco e contemporaneamente record.childEntity.childPropertycerchi di accedere , potresti finire con MARS a causa del caricamento lento del set di dati e dell'entità relazionale. Ciò causerà un'eccezione se non è abilitato nella stringa di connessione.

Soluzione

  • Ho scoperto che l'abilitazione di MARS nella stringa di connessione funziona in modo inarrestabile. Ti suggerisco di evitare MARTE a meno che non sia ben compreso e esplicitamente desiderato.

Esegui la query e archivia i risultati invocando resultList = resultSet.ToList() Questo sembra essere il modo più semplice per garantire che le entità siano in memoria.

Nei casi in cui si accede a entità correlate, è possibile che sia comunque necessario un contesto di dati. O quello, oppure puoi disabilitare i proxy di entità e le Includeentità esplicitamente correlate dal tuo DbSet.


9

La differenza principale tra "IEnumerable" e "IQueryable" riguarda la posizione in cui viene eseguita la logica del filtro. Uno viene eseguito sul lato client (in memoria) e l'altro sul database.

Ad esempio, possiamo considerare un esempio in cui abbiamo 10.000 record per un utente nel nostro database e diciamo solo 900 out che sono utenti attivi, quindi in questo caso se utilizziamo "IEnumerable", prima carica tutti i 10.000 record in memoria e quindi applica il filtro IsActive su di esso che alla fine restituisce i 900 utenti attivi.

D'altra parte, nello stesso caso, se utilizziamo "IQueryable", applicherà direttamente il filtro IsActive sul database che direttamente da lì restituirà i 900 utenti attivi.

Link di riferimento


quale è ottimizzato e leggero in termini di prestazioni?
Sitecore Sam

@Sam "IQueryable" è più preferito in termini di ottimizzazione e leggerezza.
Tabish Usman,

6

Possiamo usarli entrambi allo stesso modo e sono diversi solo nelle prestazioni.

IQueryable viene eseguito solo sul database in modo efficiente. Significa che crea un'intera query di selezione e ottiene solo i record correlati.

Ad esempio, vogliamo prendere i primi 10 clienti il ​​cui nome inizia con "Nimal". In questo caso la query selezionata verrà generata come select top 10 * from Customer where name like ‘Nimal%’.

Ma se usassimo IEnumerable, la query sarebbe simile select * from Customer where name like ‘Nimal%’e i primi dieci verranno filtrati a livello di codifica C # (ottiene tutti i record dei clienti dal database e li passa in C #).


5

Oltre alle prime 2 risposte davvero buone (di driis e di Jacob):

L'interfaccia IEnumerable si trova nello spazio dei nomi System.Collections.

L'oggetto IEnumerable rappresenta un insieme di dati in memoria e può spostarsi su questi dati solo in avanti. La query rappresentata dall'oggetto IEnumerable viene eseguita immediatamente e completamente, quindi l'applicazione riceve rapidamente i dati.

Quando viene eseguita la query, IEnumerable carica tutti i dati e, se è necessario filtrarli, il filtro stesso viene eseguito sul lato client.

L'interfaccia IQueryable si trova nello spazio dei nomi System.Linq.

L'oggetto IQueryable fornisce l'accesso remoto al database e consente di navigare tra i dati in un ordine diretto dall'inizio alla fine o nell'ordine inverso. Nel processo di creazione di una query, l'oggetto restituito è IQueryable, la query è ottimizzata. Di conseguenza, durante l'esecuzione viene consumata meno memoria, meno larghezza di banda di rete, ma allo stesso tempo può essere elaborata leggermente più lentamente di una query che restituisce un oggetto IEnumerable.

Cosa scegliere?

Se è necessario l'intero set di dati restituiti, è meglio usare IEnumerable, che fornisce la massima velocità.

Se NON è necessario l'intero set di dati restituiti, ma solo alcuni dati filtrati, è meglio usare IQueryable.


0

Oltre a quanto sopra, è interessante notare che è possibile ottenere eccezioni se si utilizza IQueryableinvece di IEnumerable:

Quanto segue funziona bene se productsè un IEnumerable:

products.Skip(-4);

Tuttavia, se productsè un IQueryablee sta tentando di accedere ai record da una tabella DB, verrà visualizzato questo errore:

L'offset specificato in una clausola OFFSET potrebbe non essere negativo.

Questo perché è stata costruita la seguente query:

SELECT [p].[ProductId]
FROM [Products] AS [p]
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS

e OFFSET non possono avere un valore negativo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.