Cómo obtener EF 6 para manejar la RESTRICCIÓN POR DEFECTO en una base de datos durante INSERT

Soy nuevo en EF (es mi primera semana), pero no soy nuevo en bases de datos ni en progtwigción. Otros han hecho preguntas similares, pero no creo que haya sido formulada con el detalle correcto o explicada por completo como se necesita explicar, así que aquí voy.

Pregunta: ¿Cómo puedo hacer que Entity Framework se ocupe adecuadamente de las columnas en una base de datos que tienen un CONSTRAINT DEFAULT definido al realizar un INSERT? Es decir, si no proporciono un valor en mi modelo durante una operación de inserción, ¿cómo puedo hacer que EF excluya esa columna de su comando TSQL INSERT generado, para que funcione la DEFICIENCIA POR DEFECTO definida por la base de datos?

Fondo

Tengo una tabla simple que creé, solo para probar Entity Framework 6 (EF6) y su interacción con las columnas que SQL Server es capaz de actualizar. Esto utiliza IDENTITY, TIMESTAMP, COMPUTED, y algunas columnas con una RESTRICCIÓN POR DEFECTO aplicada.

SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[DBUpdateTest]( [RowID] [int] IDENTITY(200,1) NOT NULL, [UserValue] [int] NOT NULL, [DefValue1] [int] NOT NULL, [DefValue2null] [int] NULL, [DefSecond] [int] NOT NULL, [CalcValue] AS (((([rowid]+[uservalue])+[defvalue1])+[defvalue2null])*[defsecond]), [RowTimestamp] [timestamp] NULL, CONSTRAINT [PK_DBUpdateTest] PRIMARY KEY CLUSTERED ( [RowID] ASC ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ) GO ALTER TABLE [dbo].[DBUpdateTest] ADD CONSTRAINT [DF_DBUpdateTest_DefValue1] DEFAULT ((200)) FOR [DefValue1] GO ALTER TABLE [dbo].[DBUpdateTest] ADD CONSTRAINT [DF_DBUpdateTest_DefValue2null] DEFAULT ((30)) FOR [DefValue2null] GO ALTER TABLE [dbo].[DBUpdateTest] ADD CONSTRAINT [DF_DBUpdateTest_DefSecond] DEFAULT (datepart(second,getdate())) FOR [DefSecond] GO 

EF6 maneja las columnas IDENTITY, TIMESTAMP y COMPUTED a la perfección, es decir, después de INSERT o UPDATE (a través de context.SaveChanges() ) EF vuelve a leer los nuevos valores en el objeto de entidad para su uso inmediato.

Sin embargo, esto no sucede con las columnas con la RESTRICCIÓN POR DEFECTO. Y por lo que puedo decir, esto se debe a que cuando EF genera el TSQL para realizar el INSERTO, proporciona el valor predeterminado común para los tipos con posibilidad de nulos o que no admiten nulos, como si esa columna no tuviera definida la RESTRICCIÓN POR DEFECTO. Así que parece claro que EF ignora completamente la posibilidad de una RESTRICCIÓN POR DEFECTO.

Aquí está mi código de EF para INSERTAR un registro DBUpdateTest (y solo actualizo una sola columna):

 DBUpdateTest myVal = new DBUpdateTest(); myVal.UserValue = RND.Next(20, 90); DB.DBUpdateTests.Add(myVal); DB.SaveChanges(); 

Aquí está el SQL generado por EF durante un INSERT to DBUpdateTest (que actualiza debidamente todas las columnas posibles):

  exec sp_executesql N'INSERT [dbo].[DBUpdateTest]([UserValue], [DefValue1], [DefValue2null], [DefSecond]) VALUES (@0, @1, NULL, @2) SELECT [RowID], [CalcValue], [RowTimestamp] FROM [dbo].[DBUpdateTest] WHERE @@ROWCOUNT > 0 AND [RowID] = scope_identity()', N'@0 int,@1 int,@2 int',@0=86,@1=0,@2=54 

Tenga en cuenta que está proporcionando claramente cuál sería el valor predeterminado para un INT NOT NULL (0) y un INT NULL (null), que supera por completo la RESTRICCIÓN POR DEFECTO.

Esto es lo que sucede cuando se ejecuta el comando EF INSERT, donde proporciona un valor NULL para la columna anulable y un CERO para la columna INT.

 RowID UserValue DefValue1 DefValue2null DefSecond CalcValue ========================================================================= 211 100 200 NULL 0 NULL 

Si, por otro lado, ejecuto esta sentencia:

 insert into DBUpdateTest (UserValue) values (100) 

Obtendré un disco como tal

 RowID UserValue DefValue1 DefValue2null DefSecond CalcValue ========================================================================= 211 100 200 30 7 3787 

Esto funciona como se esperaba por una razón: el comando TSQL INSERT no proporcionó valores para ninguna columna con un CONSTRAINT DEFAULT definido.

Por lo tanto, lo que estoy tratando de hacer es lograr que EF excluya las columnas DEFAULT CONSTRAINT de INSERT TSQL si no explico explícitamente sus valores en el objeto modelo.

Cosas que ya traté

1. ¿Reconoce la restricción por defecto? SO: cómo obtener EF para manejar una restricción predeterminada

En el método OnModelCreating() de mi clase DbContext , se recomendó que le dijera a EF que una columna con la RESTRICCIÓN POR DEFECTO es un campo COMPUTADO, que no lo es. Sin embargo, quería ver si a EF leería al menos volver a leer el valor después de INSERT (no importa, también es probable que me impida asignar un valor a esa columna, que es solo una parte de lo que hago). no quieren):

  modelBuilder.Entity() .Property(e => e.DefValue1) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed); 

Esto no funciona, y de hecho parece que no hace nada diferente en absoluto ( ED: en realidad sí funciona, vea el punto 2 ). EF aún genera el mismo TSQL, proporcionando valores predeterminados para las columnas y derrotando la base de datos en el proceso.

¿Hay algún indicador que falte, un elemento de configuración que me olvido de establecer, un atributo de función que pueda usar, algún código de clase heredado que pueda crear, para que EF “maneje las columnas DEFAULT CONSTRAINT correctamente?”

2. Haz que OnModelCreating () se ejecute? SO: OnModelCreating no se llama

Janesh (a continuación) me mostró que EF eliminará los parámetros de su comando TSQL INSERT generado si la columna está marcada con DatabaseGeneratedOption.Computed . Simplemente no estaba funcionando para mí porque aparentemente estaba usando el tipo incorrecto de cadena de conexión (!!!).

Aquí está mi App.config, y aquí está la sección donde muestro la cadena de conexión “mala” y “buena”:

     

La diferencia: el que funciona usa ProviderName de System.Data.SqlClient , el que no funciona usa System.Data.EntityClient . Aparentemente, el proveedor de SqlClient permite que se OnModelCreating() al método OnModelCreating() , lo que permite que mi uso de DatabaseGeneratedOption.Computed tenga un efecto.

========== NO SE HA RESOLVIADO ==========

El propósito de las RESTRICCIONES POR DEFECTO en las columnas es permitirme suministrar (o no suministrar) un valor, y aún así terminar con un valor válido en el lado de la base de datos. No tengo que saber que SQL Server está haciendo esto, ni tengo que saber cuál es el valor predeterminado o qué debería ser. Esto sucede completamente fuera de mi control o conocimiento.

El punto es, tengo la opción de no suministrar el valor. Puedo suministrarlo, o puedo dejar de suministrarlo, y puedo hacerlo de manera diferente para cada INSERT si es necesario.

El uso de DatabaseGeneratedOption.Computed realmente no es una opción válida para este caso porque obliga a una opción: “SIEMPRE puede proporcionar un valor (y, por lo tanto, NUNCA utilizar el mecanismo predeterminado de la base de datos), o NUNCA puede proporcionar un valor (y por lo tanto, SIEMPRE debe utilizar el mecanismo por defecto de la base de datos) “.

Además, esta opción está claramente destinada a ser utilizada solo en columnas calculadas reales, y no para columnas con RESTRICCIONES POR DEFECTO, porque una vez aplicada, la propiedad del modelo se convierte en READ-SOLO para propósitos de INSERTAR y ACTUALIZAR, porque así es como una columna calculada real trabajaría. Obviamente, esto es un obstáculo para mi elección de proporcionar o no un valor a la base de datos.

Entonces, todavía pregunto: ¿Cómo puedo hacer que EF funcione “correctamente” con las columnas de la base de datos que tienen un CONSTRAINT DEFAULT definido?

Este bit es la clave de tu pregunta:

Por lo tanto, lo que estoy tratando de hacer es conseguir que EF NO incluya las columnas DEFAULT CONSTRAINT en su INSERT TSQL si no configuro valores explícitamente para ellos en el objeto.

Entity Framework no hará eso por ti. Los campos siempre se calculan o se incluyen siempre en las inserciones y actualizaciones. Pero PUEDES escribir las clases para comportarte de la manera en que lo describiste. Debe establecer los campos (explícitamente) en los valores predeterminados en el constructor, o usar campos de respaldo.

 public class DBUpdateTest /* public partial class DBUpdateTest*/ //version for database first { private _DefValue1 = 200; private _DefValue2 = 30; public DbUpdateTest() { DefSecond = DateTime.Second; } public DefSecond { get; set; } public DefValue1 { get { return _DefValue1; } set { _DefValue1 = value; } } public DefValue2 { get { return _DefValue2; } set { _DefValue2 = value; } } } 

Si siempre inserta usando estas clases, entonces probablemente no necesite establecer el valor predeterminado en la base de datos, pero si inserta usando sql desde otro lugar, también tendrá que agregar la restricción predeterminada a la base de datos.

Estoy totalmente en desacuerdo con que DatabaseGeneratedOption.Computed no ayuda a detener el envío del campo en el comando Insertar SQL. Intenté eso con un pequeño ejemplo para verificar y funcionó.

Nota : Una vez que haya aplicado DatabaseGeneratedOption.Computed a cualquier propiedad, entonces desea poder especificar cualquier valor de EF. es decir, no puede especificar ningún valor al insertar o actualizar registros.

Modelo

 public class Person { public int Id { get; set; } public int SomeId { get; set; } public string Name { get; set; } } 

Contexto

 public class Context : DbContext { public DbSet People { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity().HasKey(d => d.Id); modelBuilder.Entity() .Property(d => d.Id) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); modelBuilder.Entity() .Property(d => d.SomeId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed); } } 

Migración

 public partial class Initial : DbMigration { public override void Up() { CreateTable( "dbo.People", c => new { Id = c.Int(nullable: false, identity: true), SomeId = c.Int(nullable: false, defaultValue:3), //I edited it mannually to assign default value 3. Name = c.String(), }) .PrimaryKey(t => t.Id); } public override void Down() { DropTable("dbo.People"); } } 

Nota : edité el valor predeterminado 3 a SomeId manualmente.

Progtwig principal :

  static void Main(string[] args) { using (Context c = new Context()) { Person p = new Person(); p.Name = "Jenish"; c.People.Add(p); c.Database.Log = Console.WriteLine; c.SaveChanges(); } } 

Conseguí la siguiente consulta registrada en mi consola:

 Opened connection at 04/15/2015 11:32:19 AM +05:30 Started transaction at 04/15/2015 11:32:19 AM +05:30 INSERT [dbo].[People]([Name]) VALUES (@0) SELECT [Id], [SomeId] FROM [dbo].[People] WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity() -- @0: 'Jenish' (Type = String, Size = -1) -- Executing at 04/15/2015 11:32:20 AM +05:30 -- Completed in 3 ms with result: SqlDataReader Committed transaction at 04/15/2015 11:32:20 AM +05:30 Closed connection at 04/15/2015 11:32:20 AM +05:30 

Tenga en cuenta que no se ha pasado algún comando al comando Insertar, sino que se está seleccionando en el comando de selección.

Alguien respondió a esta pregunta en 2013/2014, creo :). El proyecto estaba usando EF5, ok, ¡eso sí! Me dijo:

  1. Haga clic con el botón derecho en su .edmx , elija “Abrir con”, luego “Editor de XML (texto)”, y encuentre las columnas que estableció como Restricción predeterminada dentro de la sección del archivo.
  2. Luego agregue StoreGeneratedPattern="Computed" al final de estos campos.

Hecho, entonces funcionó.

Luego me mudé a EF6 y TODAVÍA funciona, pero SIN la adición hecha antes.

Ahora, me enfrento a un problema muy interesante, usando EF6, versión 6.1.3. En este mismo momento, tengo 2 tablas con una restricción predeterminada para establecer un campo booleano en 1 , ya sea que el registro esté ACTIVE o INACTIVE . Simple como eso.

En una tabla funciona, lo que significa que no tengo que agregar este StoreGeneratedPattern="Computed" a la columna dentro de .edmx . Pero, en la otra mesa, NO LO HACE; StoreGeneratedPattern="Computed" que agregar este StoreGeneratedPattern="Computed" a la misma columna que la tabla 1.

    Intereting Posts