tag - guardar imagen en sql server asp net c# mvc
Cómo obtener EF 6 para manejar la RESTRICCIÓN POR DEFECTO en una base de datos durante INSERTAR (3)
Alguien respondió a esta pregunta en 2013/2014, creo :). El proyecto estaba usando EF5, ok, ¡eso sí! Me dijo:
- 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<!-- SSDL content -->
del archivo. - 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.
Soy nuevo en EF (es mi primera semana), pero no soy nuevo en bases de datos ni en programación. Otros han hecho preguntas similares, pero no creo que se haya hecho con los detalles correctos o que hayan sido explicados por completo, por lo que es necesario explicarlos, así que aquí voy.
Pregunta: ¿Cómo obtengo Entity Framework para tratar adecuadamente 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, de modo que funcione el CONSTRAINT POR DEFECTO definido 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 perfectamente las columnas IDENTITY, TIMESTAMP y COMPUTED, 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 que admiten nulos o que no admiten nulos, como si esa columna no tuviera una RESTRICCIÓN POR DEFECTO definida. Así que parece claro que EF ignora por completo la posibilidad de una RESTRICCIÓN POR DEFECTO.
Aquí está mi código de EF para INSERTAR un registro DBUpdateTest (y solo actualizo una 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 configuro valores explícitamente para ellas 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<DBUpdateTest>()
.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 <connectionStrings>
donde muestro la cadena de conexión "mala" y "buena":
<connectionStrings>
<add name="TEST_EF6Entities_NO_WORKY" providerName="System.Data.EntityClient" connectionString="metadata=res://*/TCXModel.csdl|res://*/TCXModel.ssdl|res://*/TCXModel.msl;provider=System.Data.SqlClient;provider connection string="data source=...ConnectStringHere...;App=EntityFramework"" />
<add name="TEST_EF6Entities_IT_WORKS" providerName="System.Data.SqlClient" connectionString="data source=...ConnectStringHere...;App=EntityFramework;" />
</connectionStrings>
La diferencia: la que funciona utiliza ProviderName of System.Data.SqlClient
, la que no funciona usa System.Data.EntityClient
. Al parecer, 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 utilizar el mecanismo por defecto de la base de datos) ".
Además, esa opción está claramente destinada a ser usada 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 suministrar o no un valor a la base de datos.
Por lo tanto, aún 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 que se comporten de la manera que describes. 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. Lo intenté 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<Person> People { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>().HasKey(d => d.Id);
modelBuilder.Entity<Person>()
.Property(d => d.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Person>()
.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.
Programa 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 IID al comando Insertar; en cambio, se está seleccionando en el comando de selección.