SqlException:no se puede insertar un valor explícito para la columna de identidad

SqlException:no se puede insertar un valor explícito para la columna de identidad

Cuando tiene una tabla con una columna de identidad e intenta especificar el valor de la columna de identidad al insertar un registro, obtendrá la siguiente excepción:

Este error significa que tiene una columna de identidad en la tabla y está intentando establecer un valor para ella. Cuando tiene una columna de identidad como esta, su valor se genera automáticamente cuando la inserta, por lo que no puede pasar un valor para esta columna.

Por ejemplo, supongamos que su tabla tiene la siguiente definición:

CREATE TABLE [dbo].[Movies](
	[Id] [int] IDENTITY(1,1) NOT NULL,
	[Name] [nvarchar](500) NOT NULL,
	[YearOfRelease] [int] NOT NULL,
	[Description] [nvarchar](500) NOT NULL,
	[Director] [nvarchar](100) NOT NULL,
	[BoxOfficeRevenue] [decimal](18, 2) NOT NULL,
 CONSTRAINT [PK_Movies] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Code language: SQL (Structured Query Language) (sql)

Mostraré algunas soluciones diferentes para solucionar este problema.

Nota:Las soluciones a continuación muestran ejemplos de código usando EF Core. Si usa ADO.NET o un ORM diferente (como Dapper), las mismas soluciones también funcionarían (solo que con un código diferente).

Opción 1:no especificar la columna de identidad al insertar

La primera opción es la más simple:no intente establecer el valor de la columna de identidad:

using (var context = new StreamingServiceContext(connectionString))
{
	context.Movies.Add(new Movie()
	{
		//Id = 20,
		Name = "Godzilla",
		Description = "Nuclear lizard fights monsters",
		Director = "Gareth Edwards",
		YearOfRelease = 2014,
		BoxOfficeRevenue = 529_000_000.00m
	});

	context.SaveChanges();
}
Code language: C# (cs)

Cuando inserte el registro, SQL Server generará el valor por usted y EF Core actualizará la propiedad con el valor generado automáticamente.

Opción 2:activar IDENTITY_INSERT

En algunos casos, es posible que desee establecer explícitamente la identificación en lugar de permitir que se genere automáticamente. En este caso, deberá activar IDENTITY_INSERT, así:

using (var context = new StreamingServiceContext(connectionString))
{
	using (var transaction = context.Database.BeginTransaction())
	{
		context.Movies.Add(new Movie()
		{
			Id = 20,
			Name = "Godzilla",
			Description = "Nuclear lizard fights monsters",
			Director = "Gareth Edwards",
			YearOfRelease = 2014,
			BoxOfficeRevenue = 529_000_000.00m
		});

		context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT dbo.Movies ON;");
		context.SaveChanges();
		context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT dbo.Movies OFF;");
		transaction.Commit();
	}
}
Code language: C# (cs)

Nota:si usa EF Core, debe ejecutar la consulta dentro de una transacción para que esto funcione.

IDENTITY_INSERT solo puede estar ACTIVADO para una mesa a la vez por sesión.

Supongamos que intenta activar IDENTITY_INSERT para dos tablas a la vez:

context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT dbo.Movies ON;");
context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT dbo.Actors ON;");
Code language: C# (cs)

Obtendrá la siguiente excepción:

Esta restricción solo aplica por sesión. Si alguna otra sesión activa IDENTITY_INSERT para la tabla Actores en su sesión, puede activar IDENTITY_INSERT para Películas en una sesión diferente al mismo tiempo.

Opción 3:eliminar la especificación de IDENTIDAD de la columna

Si se encuentra en un entorno de desarrollo y no se dio cuenta de que tenía una columna de identidad hasta que se encontró con esta excepción de inserción de identidad, es probable que simplemente desee eliminar la especificación de IDENTIDAD de la columna.

Si usa EF Core para crear sus tablas, use el atributo DatabaseGenerated(DatabaseGeneratedOption.None)) para especificar que la columna no debe ser una columna de identidad.

using System.ComponentModel.DataAnnotations.Schema;

public class Movie
{
	[Key]
	[DatabaseGenerated(DatabaseGeneratedOption.None)]
	public int Id { get; set; }
	
	//rest of class
}
Code language: C# (cs)

EF Core no maneja correctamente este cambio de esquema. En lugar de intentar hacer esto como un cambio de esquema, rehaga la migración que creó la tabla.

Por ejemplo, supongamos que tiene dos migraciones:Database_v1_Init y Database_v2_CreateMoviesTable, y desea cambiar la tabla Películas para que no tenga la columna de identidad. Para rehacer la migración de Database_v2_CreateMoviesTable, realice los siguientes pasos:

  • Migrar a Database_v1_Init:
dotnet ef database update Database_v1_Init
Code language: PowerShell (powershell)
  • Elimine la última migración, que en este caso es Database_v2_CreateMoviesTable:
dotnet ef migrations remove
Code language: PowerShell (powershell)

Nota:no elimine simplemente el archivo de migración, ya que el archivo de instantánea del modelo no estará sincronizado.

  • Agregue el atributo [DatabaseGenerated(DatabaseGeneratedOption.None)] a la propiedad Movie.Id.
public class Movie
{
	[Key]
	[DatabaseGenerated(DatabaseGeneratedOption.None)]
	public int Id { get; set; }
Code language: C# (cs)
  • Vuelva a crear la base de datos de migración_v2_CreateMoviesTable:
dotnet ef migrations add Database_v2_CreateMoviesTable
Code language: PowerShell (powershell)
  • Mire el código fuente de migración generado en _Database_v2_CreateMoviesTable.cs. Primero, puede ver que no está creando la columna con una especificación de IDENTIDAD. En segundo lugar, lo único que debería hacer esta migración es crear la tabla Películas. Si está haciendo algo más, es probable que el archivo de instantánea del modelo haya entrado en un estado no válido (probablemente debido a la eliminación manual de los archivos de migración).
public partial class Database_v2_CreateMoviesTable : Migration
{
	protected override void Up(MigrationBuilder migrationBuilder)
	{
		migrationBuilder.CreateTable(
			name: "Movies",
			columns: table => new
			{
				Id = table.Column<int>(type: "int", nullable: false),
				Name = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
				YearOfRelease = table.Column<int>(type: "int", nullable: false),
				Description = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
				Director = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
				BoxOfficeRevenue = table.Column<decimal>(type: "decimal(18,2)", nullable: false)
			},
			constraints: table =>
			{
				table.PrimaryKey("PK_Movies", x => x.Id);
			});
			
			//rest of class not shown
}
Code language: C# (cs)
  • Aplicar la migración:
dotnet ef database update Database_v2_CreateMoviesTable
Code language: PowerShell (powershell)

Ahora que esa columna de identificación ya no tiene la especificación IDENTIDAD, puede insertar registros en la tabla mientras especifica un valor para la identificación.

Descargo de responsabilidad:esto no se aplica a los entornos de producción. Perderá datos si suelta y vuelve a crear la tabla. Solo recomendaría este enfoque si se encuentra en un entorno de desarrollo y está de acuerdo con la pérdida de datos.

Opción 4:si está realizando una actualización, obtenga el registro primero

Para hacer una actualización en lugar de una inserción, pídale que busque primero el registro. De lo contrario, cuando llame a SaveChanges(), EF Core generará una declaración de inserción. Si intentó especificar el valor para la columna de identidad, se encontrará con la excepción de inserción de identidad.

A continuación se explica cómo actualizar un registro buscándolo primero:

using (var context = new StreamingServiceContext(connectionString))
{
	var movie = await context.Movies.FirstOrDefaultAsync(t => t.Id == 20);
	movie.Description = "Nuclear lizard fights monsters";
	
	context.SaveChanges();
}
Code language: C# (cs)