entity-framework - tutorial - mvc entity framework español
Registrando cada cambio de datos con Entity Framework (7)
¿Has intentado agregar el procedimiento almacenado a tu modelo de entidad?
El cliente necesita registrar cada cambio de datos en una tabla de registro con el usuario real que realizó la modificación. La aplicación utiliza un usuario de SQL para acceder a la base de datos, pero debemos registrar la identificación de usuario "real".
Podemos hacer esto en t-sql escribiendo activadores para cada inserción y actualización de tabla, y usando context_info para almacenar la identificación de usuario. Pasamos el ID de usuario a un procedimiento almacenado, almacenamos el ID de usuario en el contexto, y el desencadenador podría usar esta información para escribir filas de registro en la tabla de registro.
No puedo encontrar el lugar o la forma en que o cómo puedo hacer algo similar con EF. Entonces, el objetivo principal es: si realizo un cambio en los datos a través de EF, me gustaría registrar el cambio de datos exacto en una tabla de forma semiautomática (por lo tanto, no deseo verificar si hay cambios en todos los campos). guardando el objeto). Estamos usando EntitySQL.
Desafortunadamente, tenemos que seguir con SQL 2000, por lo que la captura de cambios de datos introducida en SQL2008 no es una opción (pero quizás tampoco sea la correcta para nosotros).
¿Alguna idea, enlace o punto de partida?
[Editar] Algunas notas: mediante el uso del manejador de eventos ObjectContext.SavingChanges, puedo obtener el punto donde puedo inyectar la instrucción SQL para inicializar el contexto. Sin embargo, no puedo mezclar el EF y el SQL estándar. De modo que puedo obtener EntityConnection pero no puedo ejecutar una instrucción T-SQL usándolo. O puedo obtener la cadena de conexión de EntityConnection y crear una SqlConnection basada en ella, pero será una conexión diferente, por lo que el contexto no afectará el guardado realizado por el EF.
Intenté lo siguiente en el controlador SavingChanges:
testEntities te = (testEntities)sender;
DbConnection dc = te.Connection;
DbCommand dcc = dc.CreateCommand();
dcc.CommandType = CommandType.StoredProcedure;
DbParameter dp = new EntityParameter();
dp.ParameterName = "userid";
dp.Value = textBox1.Text;
dcc.CommandText = "userinit";
dcc.Parameters.Add(dp);
dcc.ExecuteNonQuery();
Error: el valor de EntityCommand.CommandText no es válido para un comando StoredProcedure. Lo mismo con SqlParameter en lugar de EntityParameter: SqlParameter no se puede utilizar.
StringBuilder cStr = new StringBuilder("declare @tx char(50); set @tx=''");
cStr.Append(textBox1.Text);
cStr.Append("''; declare @m binary(128); set @m = cast(@tx as binary(128)); set context_info @m;");
testEntities te = (testEntities)sender;
DbConnection dc = te.Connection;
DbCommand dcc = dc.CreateCommand();
dcc.CommandType = CommandType.Text;
dcc.CommandText = cStr.ToString();
dcc.ExecuteNonQuery();
Error: la sintaxis de la consulta no es válida.
Así que aquí estoy, atascado para crear un puente entre Entity Framework y ADO.NET. Si puedo hacerlo funcionar, publicaré una prueba de concepto.
¿Qué hay de manejo de contexto? SavingChanges ?
Finalmente, con la ayuda de Craig, aquí hay una prueba de concepto. Necesita más pruebas, pero para el primer vistazo está funcionando.
Primero: Creé dos tablas, una para los datos uno para el registro.
-- This is for the data
create table datastuff (
id int not null identity(1, 1),
userid nvarchar(64) not null default(''''),
primary key(id)
)
go
-- This is for the log
create table naplo (
id int not null identity(1, 1),
userid nvarchar(64) not null default(''''),
datum datetime not null default(''2099-12-31''),
primary key(id)
)
go
Segundo: crea un disparador para insertar.
create trigger myTrigger on datastuff for insert as
declare @User_id int,
@User_context varbinary(128),
@User_id_temp varchar(64)
select @User_context = context_info
from master.dbo.sysprocesses
where spid=@@spid
set @User_id_temp = cast(@User_context as varchar(64))
declare @insuserid nvarchar(64)
select @insuserid=userid from inserted
insert into naplo(userid, datum)
values(@User_id_temp, getdate())
go
También debe crear un desencadenador para la actualización, que será un poco más sofisticado, ya que debe verificar cada campo para ver el contenido modificado.
La tabla de registro y el desencadenador deben extenderse para almacenar la tabla y el campo que se crean / modifican, pero espero que haya captado la idea.
Tercero: cree un procedimiento almacenado que complete el ID de usuario a la información de contexto de SQL.
create procedure userinit(@userid varchar(64))
as
begin
declare @m binary(128)
set @m = cast(@userid as binary(128))
set context_info @m
end
go
Estamos listos con el lado SQL. Aquí viene la parte C #.
Crea un proyecto y agrega un EDM al proyecto. El EDM debe contener la tabla de datos (o las tablas que necesita ver para ver los cambios) y el SP.
Ahora haga algo con el objeto entidad (por ejemplo, agregue un nuevo objeto de datos) y enganche al evento SavingChanges.
using (testEntities te = new testEntities())
{
// Hook to the event
te.SavingChanges += new EventHandler(te_SavingChanges);
// This is important, because the context info is set inside a connection
te.Connection.Open();
// Add a new datastuff
datastuff ds = new datastuff();
// This is coming from a text box of my test form
ds.userid = textBox1.Text;
te.AddTodatastuff(ds);
// Save the changes
te.SaveChanges(true);
// This is not needed, only to make sure
te.Connection.Close();
}
Dentro de SavingChanges, inyectamos nuestro código para establecer la información de contexto de la conexión.
// Take my entity
testEntities te = (testEntities)sender;
// Get it''s connection
EntityConnection dc = (EntityConnection )te.Connection;
// This is important!
DbConnection storeConnection = dc.StoreConnection;
// Create our command, which will call the userinit SP
DbCommand command = storeConnection.CreateCommand();
command.CommandText = "userinit";
command.CommandType = CommandType.StoredProcedure;
// Put the user id as the parameter
command.Parameters.Add(new SqlParameter("userid", textBox1.Text));
// Execute the command
command.ExecuteNonQuery();
Entonces, antes de guardar los cambios, abrimos la conexión del objeto, inyectamos nuestro código (¡no cerremos la conexión en esta parte!) Y guardamos nuestros cambios.
¡Y no lo olvides! Esto debe ampliarse para sus necesidades de registro, y debe ser probado bien, ¡porque esto muestra solo la posibilidad!
Gracias por señalarme en la dirección correcta. Sin embargo, en mi caso, también necesito establecer la información de contexto cuando hago select statement, porque estoy consultando vistas que usan la información de contexto para controlar la seguridad de nivel de fila por usuario.
Me resultó más fácil adjuntarme al evento StateChanged de la conexión y solo observo el cambio de no abierto a abierto. Luego llamo al proceso que establece el contexto y funciona siempre, incluso si EF decide restablecer la conexión.
private int _contextUserId;
public void SomeMethod()
{
var db = new MyEntities();
db.Connection.StateChange += this.Connection_StateChange;
this._contextUserId = theCurrentUserId;
// whatever else you want to do
}
private void Connection_StateChange(object sender, StateChangeEventArgs e)
{
// only do this when we first open the connection
if (e.OriginalState == ConnectionState.Open ||
e.CurrentState != ConnectionState.Open)
return;
// use the existing open connection to set the context info
var connection = ((EntityConnection) sender).StoreConnection;
var command = connection.CreateCommand();
command.CommandText = "proc_ContextInfoSet";
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add(new SqlParameter("ContextUserID", this._contextUserId));
command.ExecuteNonQuery();
}
Hemos resuelto este problema de una manera diferente.
- Heredar una clase de la clase de contenedor de entidad generada
- Haga que la clase de entidad base sea abstracta. Puedes hacerlo por una definición de clase parcial en un archivo separado
- En la clase heredada, oculte el método SavingChanges con el suyo, usando la nueva palabra clave en la definición del método
- En su método SavingChanges: a, abra una conexión de entidad b, ejecute el procedimiento almacenado de contexto de usuario con ebtityclient c, call base.SaveChanges () d, cierre la conexión de entidad
En tu código, entonces debes usar la clase heredada.
Simplemente fuerce una ejecución de SET CONTEXT_INFO usando su DbContext u ObjectContext:
...
FileMoverContext context = new FileMoverContext();
context.SetSessionContextInfo(Environment.UserName);
...
context.SaveChanges();
FileMoverContext hereda de DbContext y tiene un método SetSessionContextInfo. Aquí está mi SetSessionContextInfo (...):
public bool SetSessionContextInfo(string infoValue)
{
try
{
if (infoValue == null)
throw new ArgumentNullException("infoValue");
string rawQuery =
@"DECLARE @temp varbinary(128)
SET @temp = CONVERT(varbinary(128), ''";
rawQuery = rawQuery + infoValue + @"'');
SET CONTEXT_INFO @temp";
this.Database.ExecuteSqlCommand(rawQuery);
return true;
}
catch (Exception e)
{
return false;
}
}
Ahora acaba de configurar un disparador de base de datos que puede acceder a CONTEXT_INFO () y establecer un campo de base de datos que lo usa.
Tenía un escenario similar, que resolví mediante los siguientes pasos:
Primero crea un repositorio genérico para todas las operaciones de CRUD, como por ejemplo, que siempre es un buen enfoque. public class GenericRepository: IGenericRepository donde T: clase
Ahora escriba sus acciones como "actualización virtual void pública (T entityToUpdate)".
- Donde sea que requirió la tala / Auditoría; simplemente llame a una función definida por el usuario de la siguiente manera "LogEntity (entityToUpdate," U ");".
- Consulte a continuación el archivo / clase pegado para definir la función "LogEntity". En esta función, en caso de actualización y eliminación obtendríamos la entidad anterior a través de la clave primaria para insertarla en la tabla de auditoría. Para identificar la clave principal y obtener su valor, utilicé la reflexión.
Encuentre la referencia de la clase completa a continuación:
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
internal SampleDBContext Context;
internal DbSet<T> DbSet;
/// <summary>
/// Constructor to initialize type collection
/// </summary>
/// <param name="context"></param>
public GenericRepository(SampleDBContext context)
{
Context = context;
DbSet = context.Set<T>();
}
/// <summary>
/// Get query on current entity
/// </summary>
/// <returns></returns>
public virtual IQueryable<T> GetQuery()
{
return DbSet;
}
/// <summary>
/// Performs read operation on database using db entity
/// </summary>
/// <param name="filter"></param>
/// <param name="orderBy"></param>
/// <param name="includeProperties"></param>
/// <returns></returns>
public virtual IEnumerable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>,
IOrderedQueryable<T>> orderBy = null, string includeProperties = "")
{
IQueryable<T> query = DbSet;
if (filter != null)
{
query = query.Where(filter);
}
query = includeProperties.Split(new[] { '','' }, StringSplitOptions.RemoveEmptyEntries).Aggregate(query, (current, includeProperty) => current.Include(includeProperty));
if (orderBy == null)
return query.ToList();
else
return orderBy(query).ToList();
}
/// <summary>
/// Performs read by id operation on database using db entity
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public virtual T GetById(object id)
{
return DbSet.Find(id);
}
/// <summary>
/// Performs add operation on database using db entity
/// </summary>
/// <param name="entity"></param>
public virtual void Insert(T entity)
{
//if (!entity.GetType().Name.Contains("AuditLog"))
//{
// LogEntity(entity, "I");
//}
DbSet.Add(entity);
}
/// <summary>
/// Performs delete by id operation on database using db entity
/// </summary>
/// <param name="id"></param>
public virtual void Delete(object id)
{
T entityToDelete = DbSet.Find(id);
Delete(entityToDelete);
}
/// <summary>
/// Performs delete operation on database using db entity
/// </summary>
/// <param name="entityToDelete"></param>
public virtual void Delete(T entityToDelete)
{
if (!entityToDelete.GetType().Name.Contains("AuditLog"))
{
LogEntity(entityToDelete, "D");
}
if (Context.Entry(entityToDelete).State == EntityState.Detached)
{
DbSet.Attach(entityToDelete);
}
DbSet.Remove(entityToDelete);
}
/// <summary>
/// Performs update operation on database using db entity
/// </summary>
/// <param name="entityToUpdate"></param>
public virtual void Update(T entityToUpdate)
{
if (!entityToUpdate.GetType().Name.Contains("AuditLog"))
{
LogEntity(entityToUpdate, "U");
}
DbSet.Attach(entityToUpdate);
Context.Entry(entityToUpdate).State = EntityState.Modified;
}
public void LogEntity(T entity, string action = "")
{
try
{
//*********Populate the audit log entity.**********
var auditLog = new AuditLog();
auditLog.TableName = entity.GetType().Name;
auditLog.Actions = action;
auditLog.NewData = Newtonsoft.Json.JsonConvert.SerializeObject(entity);
auditLog.UpdateDate = DateTime.Now;
foreach (var property in entity.GetType().GetProperties())
{
foreach (var attribute in property.GetCustomAttributes(false))
{
if (attribute.GetType().Name == "KeyAttribute")
{
auditLog.TableIdValue = Convert.ToInt32(property.GetValue(entity));
var entityRepositry = new GenericRepository<T>(Context);
var tempOldData = entityRepositry.GetById(auditLog.TableIdValue);
auditLog.OldData = tempOldData != null ? Newtonsoft.Json.JsonConvert.SerializeObject(tempOldData) : null;
}
if (attribute.GetType().Name == "CustomTrackAttribute")
{
if (property.Name == "BaseLicensingUserId")
{
auditLog.UserId = ValueConversion.ConvertValue(property.GetValue(entity).ToString(), 0);
}
}
}
}
//********Save the log in db.*********
new UnitOfWork(Context, null, false).AuditLogRepository.Insert(auditLog);
}
catch (Exception ex)
{
Logger.LogError(string.Format("Error occured in [{0}] method of [{1}]", Logger.GetCurrentMethod(), this.GetType().Name), ex);
}
}
}
CREATE TABLE [dbo].[AuditLog](
[AuditId] [BIGINT] IDENTITY(1,1) NOT NULL,
[TableName] [nvarchar](250) NULL,
[UserId] [int] NULL,
[Actions] [nvarchar](1) NULL,
[OldData] [text] NULL,
[NewData] [text] NULL,
[TableIdValue] [BIGINT] NULL,
[UpdateDate] [datetime] NULL,
CONSTRAINT [PK_DBAudit] PRIMARY KEY CLUSTERED
(
[AuditId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY =
OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]