2014-06-11 4 views
11

Sto utilizzando EF6 con un progetto di database prima. Abbiamo l'obbligo di utilizzare sequenze che erano una funzionalità introdotta in SQL Server 2012 (credo).Entity Framework 6 e sequenze SQL Server

Sul tavolo la colonna di identità ha un valore di default impostato con:

(NEXT VALUE FOR [ExhibitIdentity]) 

Questo viene utilizzato come abbiamo due tabelle che memorizzano le informazioni per l'esposizione a reparti separati ma abbiamo bisogno l'identità di essere unico in tutta entrambi le tabelle vengono quindi utilizzate come riferimento in molte altre tabelle comuni condivise.

Il mio problema sta usando questo all'interno di Entity Framework, ho cercato su Google ma non sono riuscito a trovare molte informazioni in relazione al fatto che EF6 le supporti. Ho provato a impostare EFdesigner su EFdesigner su Identity ma quando si salva questo si lamenta che le righe pari a zero sono state interessate poiché utilizza scope_identity per verificare se l'inserimento ha avuto esito positivo, ma poiché stiamo utilizzando sequenze, questo viene restituito come null.

Impostandolo su calcolato genera un errore che dice che dovrei impostarlo sull'identità e impostarlo su none fa sì che inserisca 0 come valore id e fallisca.

Devo chiamare una funzione/procedura per ottenere la sequenza successiva e quindi assegnarla al valore id prima di salvare il record?

Qualsiasi aiuto è molto apprezzato.

risposta

16

E 'chiaro che non puoi sfuggire a questo catch-22 giocando con DatabaseGeneratedOption s.

L'opzione migliore, come suggerito, è impostare DatabaseGeneratedOption.None e ottenere il valore successivo dalla sequenza (ad esempio come this question) proprio prima di salvare un nuovo record. Quindi assegnarlo al valore Id e salvare. Questo è sicuro per la concorrenza, perché sarai l'unico a tracciare quel valore specifico dalla sequenza (supponiamo che nessuno resetta la sequenza).

Tuttavia, v'è un possibile mod ...

uno cattivo, e mi dovrebbe fermarsi qui ...

EF 6 ha introdotto il comando intercettore API. Permette di manipolare i comandi SQL di EF e i loro risultati prima e dopo l'esecuzione dei comandi. Ovviamente non dovremmo manomettere questi comandi, dovremmo?

Beh ... se guardiamo a un comando di inserimento che viene eseguito quando DatabaseGeneratedOption.Identity è impostato, si vede qualcosa di simile:

INSERT [dbo].[Person]([Name]) VALUES (@0) 
SELECT [Id] 
FROM [dbo].[Person] 
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity() 

Il comando SELECT viene utilizzato per recuperare il valore della chiave primaria generata dal database e impostare la proprietà Identity del nuovo oggetto su questo valore. Ciò consente a EF di utilizzare questo valore nelle istruzioni di inserimento successive che fanno riferimento a questo nuovo oggetto da una chiave esterna nella stessa transazione.

Quando la chiave primaria viene generata da un valore predefinito che acquisisce il suo valore da una sequenza (come si fa), è evidente che non esiste scope_identity().V'è tuttavia un valore corrente della sequenza, che si trova da un comando come

SELECT current_value FROM sys.sequences WHERE name = 'PersonSequence' 

Se solo potessimo fare EF eseguire questo comando dopo l'inserto al posto di scope_identity()!

Bene, possiamo.

In primo luogo, dobbiamo creare una classe che implementa IDbCommandInterceptor, o eredita dalla implementazione di default DbCommandInterceptor:

using System.Data.Entity.Infrastructure.Interception; 

class SequenceReadCommandInterceptor : DbCommandInterceptor 
{ 
    public override void ReaderExecuting(DbCommand command 
      , DbCommandInterceptionContext<DbDataReader> interceptionContext) 
    { 
    } 
} 

Aggiungiamo questa classe al contesto intercettazione dal comando

DbInterception.Add(new SequenceReadCommandInterceptor()); 

Il comando ReaderExecuting viene eseguito immediatamente prima che venga eseguito command. Se si tratta di un comando INSERT con una colonna Identity, il suo testo è simile al comando precedente. Ora abbiamo potuto sostituire la parte scope_identity() dalla query ottenendo il valore della sequenza corrente:

command.CommandText = command.CommandText 
          .Replace("scope_identity()", 
          "(SELECT current_value FROM sys.sequences 
           WHERE name = 'PersonSequence')"); 

Ora il comando sarà simile

INSERT [dbo].[Person]([Name]) VALUES (@0) 
SELECT [Id] 
FROM [dbo].[Person] 
WHERE @@ROWCOUNT > 0 AND [Id] = 
    (SELECT current_value FROM sys.sequences 
    WHERE name = 'PersonSequence') 

E se corriamo questo, la cosa divertente è : Funziona. Subito dopo il comando SaveChanges il nuovo oggetto ha ricevuto il suo valore Id persistito.

Realmente non penso che questo sia pronto per la produzione. Dovresti modificare il comando quando si tratta di un comando di inserimento, scegliere la sequenza corretta in base all'entità inserita, tutto tramite la manipolazione di stringhe sporche in un luogo piuttosto oscuro. E Non so se con una concorrenza pesante si otterrà sempre il giusto valore di sequenza. Ma chissà, forse una prossima versione di EF supporterà questo fuori dagli schemi.

+2

Forse il ripping dell'intero 'select' e l'inserimento di una clausola' output inserted.Id' appena prima dei 'valori'potrebbero invece eseguire il trucco, in un modo più robusto. –

+2

@Frederic Interessante, ma l'istruzione select viene utilizzata anche per ottenere i valori delle colonne calcolate (se presenti). E la clausola 'output' deve avere una variabile' in' se la tabella ha trigger, rendendo molto più complesso (se possibile) ottenere il valore id inserito. –

+2

L'istruzione 'output' può restituire anche le colonne calcolate. Buon punto per supportare il trigger, dobbiamo usare 'into'. Quindi la modifica dovrebbe essere aggiunta come prima riga 'declare @InsId table (Id int)', inserendo 'output inserted.Id in @ InsId', mantenendo' select' ma aumentato con 'inner join @InsId ii su [Person] . [Id] = ii.Id', e infine rimuovendo la condizione 'e [Id] = ...'. Inizia ad essere un po 'pesante per un hack già brutto. –