客户需要将每个数据更改记录到与进行修改的实际用户的日志记录表中.应用程序使用一个SQL用户来访问数据库,但我们需要记录"真实"用户ID.
我们可以在t-sql中通过为每个表插入和更新编写触发器,并使用context_info来存储用户ID来完成此操作.我们将用户标识传递给存储过程,将用户标识存储在contextinfo中,触发器可以使用此信息将日志行写入日志表.
我无法找到使用EF在哪里或如何做类似的地方或方式.所以主要目标是:如果我通过EF对数据进行更改,我想以半自动的方式将确切的数据更改记录到表中(因此我不想在每个字段之前检查更改)保存对象).我们正在使用EntitySQL.
不幸的是,我们必须坚持SQL 2000,因此SQL2008中引入的数据更改捕获不是一个选项(但也许这对我们来说也不是正确的方法).
任何想法,链接或起点?
[编辑] 一些注意事项:通过使用ObjectContext.SavingChanges事件处理程序,我可以得到我可以注入SQL语句来初始化contextinfo的点.但是我不能混合使用EF和标准SQL.所以我可以获得EntityConnection但我无法使用它执行T-SQL语句.或者我可以获取EntityConnection的连接字符串并基于它创建一个SqlConnection,但它将是一个不同的连接,因此contextinfo不会影响EF的保存.
我在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();
错误:EntityCommand.CommandText的值对StoredProcedure命令无效.与SqlParameter相同而不是EntityParameter:SqlParameter不能使用.
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();
错误:查询语法无效.
所以在这里,我坚持在Entity Framework和ADO.NET之间建立一座桥梁.如果我可以使它工作,我会发布一个概念证明.
谢谢你指点我正确的方向.但是,在我的情况下,我还需要在执行select语句时设置上下文信息,因为我查询使用上下文信息来控制用户的行级安全性的视图.
我发现最容易附加到连接的StateChanged事件,只是注意从非打开到打开的更改.然后我调用设置上下文的proc,它每次都有效,即使EF决定重置连接.
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(); }
如何处理Context.SavingChanges?
最后在克雷格的帮助下,这是一个概念证明.它需要更多的测试,但首先看它是有效的.
第一:我创建了两个表,一个用于记录数据.
-- 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
第二:创建插入触发器.
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
您还应该创建一个更新触发器,这将更加复杂,因为它需要检查每个字段是否有更改的内容.
应该扩展日志表和触发器以存储创建/更改的表和字段,但我希望您有这个想法.
第三步:创建一个存储过程,将用户id填入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
我们已经准备好了SQL端.这是C#部分.
创建项目并将EDM添加到项目中.EDM应包含数据存储表(或需要监视更改的表)和SP.
现在对实体对象执行某些操作(例如添加新的数据填充对象)并挂钩到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(); }
在SavingChanges中,我们注入代码来设置连接的上下文信息.
// 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();
因此,在保存更改之前,我们打开对象的连接,注入我们的代码(不要关闭此部分中的连接!)并保存我们的更改.
别忘了!这需要针对您的日志记录需求进行扩展,并且需要经过充分测试,因为这只显示了可能性!