Stored procedure code first insert, update ed delete

Nota

Solo EF6 e versioni successive: funzionalità, API e altri argomenti discussi in questa pagina sono stati introdotti in Entity Framework 6. Se si usa una versione precedente, le informazioni qui riportate, o parte di esse, non sono applicabili.

Per impostazione predefinita, Code First configurerà tutte le entità per eseguire comandi di inserimento, aggiornamento ed eliminazione usando l'accesso diretto alle tabelle. A partire da EF6 è possibile configurare il modello Code First in modo da usare stored procedure per alcune o tutte le entità nel modello.

Mapping di entità di base

È possibile acconsentire esplicitamente all'uso di stored procedure per l'inserimento, l'aggiornamento e l'eliminazione tramite l'API Fluent.

modelBuilder
  .Entity<Blog>()
  .MapToStoredProcedures();

In questo modo Code First userà alcune convenzioni per compilare la forma prevista delle stored procedure nel database.

  • Tre stored procedure denominate <type_name>_Insert, <type_name>_Update e <type_name>_Delete , ad esempio Blog_Insert, Blog_Update e Blog_Delete.
  • I nomi dei parametri corrispondono ai nomi delle proprietà.

    Nota

    Se si usa HasColumnName() o l'attributo Column per rinominare la colonna per una determinata proprietà, questo nome viene usato per i parametri anziché per il nome della proprietà.

  • La stored procedure di inserimento avrà un parametro per ogni proprietà, ad eccezione di quelle contrassegnate come generate dall'archivio (identità o calcolate). La stored procedure deve restituire un set di risultati con una colonna per ogni proprietà generata dall'archivio.
  • La stored procedure di aggiornamento avrà un parametro per ogni proprietà, ad eccezione di quelle contrassegnate con un modello generato dall'archivio "Calcolato". Per informazioni dettagliate, alcuni token di concorrenza richiedono un parametro per il valore originale. Per informazioni dettagliate, vedere la sezione Token di concorrenza. La stored procedure deve restituire un set di risultati con una colonna per ogni proprietà calcolata.
  • La stored procedure delete deve avere un parametro per il valore della chiave dell'entità (o più parametri se l'entità ha una chiave composita). Inoltre, la procedura di eliminazione deve avere parametri per qualsiasi chiave esterna di associazione indipendente nella tabella di destinazione (relazioni che non dispongono di proprietà di chiave esterna corrispondenti dichiarate nell'entità). Per informazioni dettagliate, alcuni token di concorrenza richiedono un parametro per il valore originale. Per informazioni dettagliate, vedere la sezione Token di concorrenza.

Usare la classe seguente come esempio:

public class Blog  
{  
  public int BlogId { get; set; }  
  public string Name { get; set; }  
  public string Url { get; set; }  
}

Le stored procedure predefinite sono:

CREATE PROCEDURE [dbo].[Blog_Insert]  
  @Name nvarchar(max),  
  @Url nvarchar(max)  
AS  
BEGIN
  INSERT INTO [dbo].[Blogs] ([Name], [Url])
  VALUES (@Name, @Url)

  SELECT SCOPE_IDENTITY() AS BlogId
END
CREATE PROCEDURE [dbo].[Blog_Update]  
  @BlogId int,  
  @Name nvarchar(max),  
  @Url nvarchar(max)  
AS  
  UPDATE [dbo].[Blogs]
  SET [Name] = @Name, [Url] = @Url     
  WHERE BlogId = @BlogId;
CREATE PROCEDURE [dbo].[Blog_Delete]  
  @BlogId int  
AS  
  DELETE FROM [dbo].[Blogs]
  WHERE BlogId = @BlogId

Override delle impostazioni predefinite

È possibile eseguire l'override di parte o di tutti gli elementi configurati per impostazione predefinita.

È possibile modificare il nome di una o più stored procedure. In questo esempio viene rinominata solo la stored procedure di aggiornamento.

modelBuilder  
  .Entity<Blog>()  
  .MapToStoredProcedures(s =>  
    s.Update(u => u.HasName("modify_blog")));

In questo esempio vengono rinominate tutte e tre le stored procedure.

modelBuilder  
  .Entity<Blog>()  
  .MapToStoredProcedures(s =>  
    s.Update(u => u.HasName("modify_blog"))  
     .Delete(d => d.HasName("delete_blog"))  
     .Insert(i => i.HasName("insert_blog")));

In questi esempi le chiamate vengono concatenate, ma è anche possibile usare la sintassi del blocco lambda.

modelBuilder  
  .Entity<Blog>()  
  .MapToStoredProcedures(s =>  
    {  
      s.Update(u => u.HasName("modify_blog"));  
      s.Delete(d => d.HasName("delete_blog"));  
      s.Insert(i => i.HasName("insert_blog"));  
    });

In questo esempio viene rinominato il parametro per la proprietà BlogId nella stored procedure di aggiornamento.

modelBuilder  
  .Entity<Blog>()  
  .MapToStoredProcedures(s =>  
    s.Update(u => u.Parameter(b => b.BlogId, "blog_id")));

Queste chiamate sono tutte concatenabili e componibili. Di seguito è riportato un esempio che rinomina tutte e tre le stored procedure e i relativi parametri.

modelBuilder  
  .Entity<Blog>()  
  .MapToStoredProcedures(s =>  
    s.Update(u => u.HasName("modify_blog")  
                   .Parameter(b => b.BlogId, "blog_id")  
                   .Parameter(b => b.Name, "blog_name")  
                   .Parameter(b => b.Url, "blog_url"))  
     .Delete(d => d.HasName("delete_blog")  
                   .Parameter(b => b.BlogId, "blog_id"))  
     .Insert(i => i.HasName("insert_blog")  
                   .Parameter(b => b.Name, "blog_name")  
                   .Parameter(b => b.Url, "blog_url")));

È anche possibile modificare il nome delle colonne nel set di risultati che contiene valori generati dal database.

modelBuilder
  .Entity<Blog>()
  .MapToStoredProcedures(s =>
    s.Insert(i => i.Result(b => b.BlogId, "generated_blog_identity")));
CREATE PROCEDURE [dbo].[Blog_Insert]  
  @Name nvarchar(max),  
  @Url nvarchar(max)  
AS  
BEGIN
  INSERT INTO [dbo].[Blogs] ([Name], [Url])
  VALUES (@Name, @Url)

  SELECT SCOPE_IDENTITY() AS generated_blog_id
END

Relazioni senza chiave esterna nella classe (associazioni indipendenti)

Quando una proprietà di chiave esterna viene inclusa nella definizione della classe, il parametro corrispondente può essere rinominato nello stesso modo di qualsiasi altra proprietà. Quando una relazione esiste senza una proprietà di chiave esterna nella classe , il nome del parametro predefinito è <navigation_property_name>_<primary_key_name>.

Ad esempio, le definizioni di classe seguenti generano un parametro Blog_BlogId previsto nelle stored procedure per inserire e aggiornare Post.

public class Blog  
{  
  public int BlogId { get; set; }  
  public string Name { get; set; }  
  public string Url { get; set; }

  public List<Post> Posts { get; set; }  
}  

public class Post  
{  
  public int PostId { get; set; }  
  public string Title { get; set; }  
  public string Content { get; set; }  

  public Blog Blog { get; set; }  
}

Override delle impostazioni predefinite

È possibile modificare i parametri per le chiavi esterne non incluse nella classe specificando il percorso della proprietà chiave primaria al metodo Parameter.

modelBuilder
  .Entity<Post>()  
  .MapToStoredProcedures(s =>  
    s.Insert(i => i.Parameter(p => p.Blog.BlogId, "blog_id")));

Se non si dispone di una proprietà di navigazione sull'entità dipendente (ad esempio nessuna proprietà Post.Blog), è possibile usare il metodo Association per identificare l'altra estremità della relazione e quindi configurare i parametri che corrispondono a ognuna delle proprietà chiave.

modelBuilder
  .Entity<Post>()  
  .MapToStoredProcedures(s =>  
    s.Insert(i => i.Navigation<Blog>(  
      b => b.Posts,  
      c => c.Parameter(b => b.BlogId, "blog_id"))));

Token di concorrenza

Le stored procedure di aggiornamento ed eliminazione possono anche dover gestire la concorrenza:

  • Se l'entità contiene token di concorrenza, la stored procedure può facoltativamente avere un parametro di output che restituisce il numero di righe aggiornate/eliminate (righe interessate). Tale parametro deve essere configurato usando il metodo RowsAffectedParameter.
    Per impostazione predefinita, Entity Framework usa il valore restituito da ExecuteNonQuery per determinare il numero di righe interessate. La specifica di un parametro di output interessato dalle righe è utile se si esegue una logica in sproc che comporta il valore restituito di ExecuteNonQuery non corretto (dal punto di vista di ENTITY) alla fine dell'esecuzione.
  • Per ogni token di concorrenza sarà presente un parametro denominato <property_name>_Original (ad esempio, Timestamp_Original ). Verrà passato il valore originale di questa proprietà, ovvero il valore quando viene eseguita una query dal database.
    • I token di concorrenza calcolati dal database, ad esempio i timestamp, avranno solo un parametro di valore originale.
    • Le proprietà non calcolate impostate come token di concorrenza avranno anche un parametro per il nuovo valore nella procedura di aggiornamento. In questo modo vengono usate le convenzioni di denominazione già descritte per i nuovi valori. Un esempio di tale token consiste nell'usare l'URL di un blog come token di concorrenza, il nuovo valore è obbligatorio perché può essere aggiornato a un nuovo valore dal codice (a differenza di un token Timestamp che viene aggiornato solo dal database).

Si tratta di una classe di esempio e di aggiornamento della stored procedure con un token di concorrenza timestamp.

public class Blog  
{  
  public int BlogId { get; set; }  
  public string Name { get; set; }  
  public string Url { get; set; }  
  [Timestamp]
  public byte[] Timestamp { get; set; }
}
CREATE PROCEDURE [dbo].[Blog_Update]  
  @BlogId int,  
  @Name nvarchar(max),  
  @Url nvarchar(max),
  @Timestamp_Original rowversion  
AS  
  UPDATE [dbo].[Blogs]
  SET [Name] = @Name, [Url] = @Url     
  WHERE BlogId = @BlogId AND [Timestamp] = @Timestamp_Original

Ecco una classe di esempio e una stored procedure di aggiornamento con token di concorrenza non calcolata.

public class Blog  
{  
  public int BlogId { get; set; }  
  public string Name { get; set; }  
  [ConcurrencyCheck]
  public string Url { get; set; }  
}
CREATE PROCEDURE [dbo].[Blog_Update]  
  @BlogId int,  
  @Name nvarchar(max),  
  @Url nvarchar(max),
  @Url_Original nvarchar(max),
AS  
  UPDATE [dbo].[Blogs]
  SET [Name] = @Name, [Url] = @Url     
  WHERE BlogId = @BlogId AND [Url] = @Url_Original

Override delle impostazioni predefinite

Facoltativamente, è possibile introdurre un parametro interessato da righe.

modelBuilder  
  .Entity<Blog>()  
  .MapToStoredProcedures(s =>  
    s.Update(u => u.RowsAffectedParameter("rows_affected")));

Per i token di concorrenza calcolati del database, in cui viene passato solo il valore originale, è sufficiente usare il meccanismo di ridenominazione dei parametri standard per rinominare il parametro per il valore originale.

modelBuilder  
  .Entity<Blog>()  
  .MapToStoredProcedures(s =>  
    s.Update(u => u.Parameter(b => b.Timestamp, "blog_timestamp")));

Per i token di concorrenza non calcolati, in cui vengono passati sia il valore originale che quello nuovo, è possibile usare un overload di Parameter che consente di specificare un nome per ogni parametro.

modelBuilder
 .Entity<Blog>()
 .MapToStoredProcedures(s => s.Update(u => u.Parameter(b => b.Url, "blog_url", "blog_original_url")));

Relazioni many-to-many

In questa sezione verranno usate le classi seguenti.

public class Post  
{  
  public int PostId { get; set; }  
  public string Title { get; set; }  
  public string Content { get; set; }  

  public List<Tag> Tags { get; set; }  
}  

public class Tag  
{  
  public int TagId { get; set; }  
  public string TagName { get; set; }  

  public List<Post> Posts { get; set; }  
}

È possibile eseguire il mapping di molte a molte relazioni alle stored procedure con la sintassi seguente.

modelBuilder  
  .Entity<Post>()  
  .HasMany(p => p.Tags)  
  .WithMany(t => t.Posts)  
  .MapToStoredProcedures();

Se non viene specificata alcuna altra configurazione, per impostazione predefinita viene utilizzata la forma stored procedure seguente.

  • Due stored procedure denominate <type_one type_two><>_Insert e< type_one><type_two>_Delete, ad esempio PostTag_Insert e PostTag_Delete.
  • I parametri saranno i valori chiave per ogni tipo. Nome di ogni parametro type_name<>_<property_name>, ad esempio Post_PostId e Tag_TagId.

Ecco un esempio di inserimento e aggiornamento delle stored procedure.

CREATE PROCEDURE [dbo].[PostTag_Insert]  
  @Post_PostId int,  
  @Tag_TagId int  
AS  
  INSERT INTO [dbo].[Post_Tags] (Post_PostId, Tag_TagId)   
  VALUES (@Post_PostId, @Tag_TagId)
CREATE PROCEDURE [dbo].[PostTag_Delete]  
  @Post_PostId int,  
  @Tag_TagId int  
AS  
  DELETE FROM [dbo].[Post_Tags]    
  WHERE Post_PostId = @Post_PostId AND Tag_TagId = @Tag_TagId

Override delle impostazioni predefinite

I nomi delle procedure e dei parametri possono essere configurati in modo analogo alle stored procedure di entità.

modelBuilder  
  .Entity<Post>()  
  .HasMany(p => p.Tags)  
  .WithMany(t => t.Posts)  
  .MapToStoredProcedures(s =>  
    s.Insert(i => i.HasName("add_post_tag")  
                   .LeftKeyParameter(p => p.PostId, "post_id")  
                   .RightKeyParameter(t => t.TagId, "tag_id"))  
     .Delete(d => d.HasName("remove_post_tag")  
                   .LeftKeyParameter(p => p.PostId, "post_id")  
                   .RightKeyParameter(t => t.TagId, "tag_id")));