执行entity framework时,CONTEXT_INFO丢失.SaveChanges(重载)

解:

你可以找到: 这里

  • 如果在操作之前尚未打开连接,则对象上下文将打开该连接。 如果对象上下文在操作期间打开连接,则它将始终在操作完成时关闭连接。
  • 如果手动打开连接,则对象上下文不会将其关闭。 调用Close或Dispose将关闭连接。

问题是EF会打开和关闭SetUserContext的连接,所以我会松开CONTEXT_INFO。 为了保持它,我需要手动打开连接并在SaveChanges之后关闭它

public int SaveChanges(string modifierId) { Database.Connection.Open(); SetUserContext(modifierId); var changes = base.SaveChanges(); Database.Connection.Close(); return changes; } 

题 :

该系统适用于数据仓库。 数据库必须知道谁修改它并在Audit表中保存任何更改。

为了达到这个结果,我主要依靠触发器和程序:

此函数将userId保存在CONTEXT_INFO中:

 CREATE PROCEDURE [dbo].[SetUserContext] @userId NVARCHAR (64) AS BEGIN SET NOCOUNT ON; DECLARE @context VARBINARY(128) SET @context = CONVERT(VARBINARY(128), @userId) SET CONTEXT_INFO @context END 

这个可以在任何地方使用来获取userId:

 CREATE FUNCTION [dbo].[GetUserContext] () RETURNS NVARCHAR (64) AS BEGIN RETURN CONVERT(NVARCHAR (64), CONTEXT_INFO()) END 

例如,在我的触发器中,我有:

 CREATE TRIGGER UpdateUser ON [dbo].[Users] FOR UPDATE AS BEGIN INSERT INTO [Audit_Users] SELECT * , dbo.GetUserContext() , GETUTCDATE() , 0 FROM inserted END GO CREATE TABLE [dbo].[Users] ( [Id] NVARCHAR (64) NOT NULL, [FirstName] NVARCHAR (255) NOT NULL, [LastName] NVARCHAR (255) NOT NULL, [BirthDate] DATE NOT NULL, [Type] INT NOT NULL, [Status] INT NOT NULL, [CreatorId] NVARCHAR (64) NOT NULL, PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [FK_Users_ToStatus] FOREIGN KEY ([Status]) REFERENCES [dbo].[StatusUsers] ([Id]), CONSTRAINT [FK_Users_ToCreator] FOREIGN KEY ([CreatorId]) REFERENCES [dbo].[Users] ([Id]), CONSTRAINT [FK_Users_ToType] FOREIGN KEY ([Type]) REFERENCES [dbo].[TypeUsers] ([Id]) ); CREATE TABLE [dbo].[Audit_Users] ( [Id] INT IDENTITY (1, 1) NOT NULL, [UserId] NVARCHAR (64) NOT NULL, [FirstName] NVARCHAR (255) NOT NULL, [LastName] NVARCHAR (255) NOT NULL, [BirthDate] DATE NOT NULL, [Type] INT NOT NULL, [Status] INT NOT NULL, [CreatorId] NVARCHAR (64) NOT NULL, [ModifierId] NVARCHAR (64) NOT NULL, [Date] DATETIME NOT NULL, [Deleted] INT NOT NULL, PRIMARY KEY CLUSTERED ([Id] ASC) ); 

当我用sql请求测试并且一切正常时,一切似乎工作正常。 问题是我需要使用Entity Framework在我的WCF服务中调用它们。 而现在这就是麻烦开始的地方。 我使用重载方法通过实体设置CONTEXT_INFO

  public int SaveChanges(string modifierId) { SetUserContext(modifierId); return base.SaveChanges(); } 

但是当base.SaveChanges(); 获取调用,我得到:

无法将值NULL插入“ModifierId”列,表’dbo.Audit_Users’; 列不允许空值。 INSERT失败。 该语句已终止。

这表明我丢失了CONTEXT_INFO。 我调试了(添加表并修改setContext过程,并使用正确的值调用过程)。

谢谢你的帮助我不是数据库专家,它可能是非常简单但我被困在这里..

按照要求:

  public partial class Entities : DbContext { public Entities() : base("name=Entities") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { throw new UnintentionalCodeFirstException(); } public virtual DbSet
Addresses { get; set; } public virtual DbSet Contacts { get; set; } public virtual DbSet Emails { get; set; } public virtual DbSet Files { get; set; } public virtual DbSet StatusUsers { get; set; } public virtual DbSet TypeCommons { get; set; } public virtual DbSet TypeFiles { get; set; } public virtual DbSet TypeUsers { get; set; } public virtual DbSet Users { get; set; } public virtual DbSet Workflows { get; set; } public virtual int SetUserContext(string userId) { var userIdParameter = userId != null ? new ObjectParameter("userId", userId) : new ObjectParameter("userId", typeof(string)); return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("SetUserContext", userIdParameter); } }

创建用户:

 public UserDto Create(string id, string firstName, string lastName, DateTime birthdate, string type, string modifierId) { var userToReturn = new UserDto { Id = id, FirstName = firstName, LastName = lastName, Birthdate = birthdate, CreatorId = modifierId, Status = "Created", Type = type }; using (var db = ContextFactory.GetEntities()) { var user = Mapper.Map(userToReturn); using (var transaction = new TransactionScope()) // this creates a new transaction { db.Users.Add(user); db.SetUserContext(modifierId); if (db.SaveChanges() == 1) { userToReturn = Mapper.Map(user); userToReturn.Type = type; userToReturn.Status = "Created"; transaction.Complete(); } } } return userToReturn; } 

根据文件 CONTEXT_INFO

返回使用SET CONTEXT_INFO语句为当前会话或批处理设置的context_info值。

“会话或批处理”或多或少与.NET托管连接相对应。 这是了解EF连接管理有点帮助的地方。

默认的EF行为是非常安全地打开和关闭数据库连接,因为.NET连接池使这种方法非常有效。 在您的情况下,这将意味着您的初始存储过程调用发生在与后续EF保存操作不同的“会话或批处理”中。

修复

这很容易修复:您只需要对数据库连接进行显式控制。 您可以通过为上下文对象提供构造函数重载来提供与基本DbContext类的开放连接,或者在存储过程调用之前手动打开连接。

这个可以吗?

[你能]建议一个更漂亮的方法吗?

使用EntityFramework的重点是避免必须管理SQL Connections。 我觉得某处出了问题。

将EF代码从底层实现中抽象出来通常是不现实的。 我不确定它是否特别令人满意。 对于工作层的存储库/单元,这种抽象通常更好。

EF,IMHO的“重点”是避免大量的样板代码在数据库的原始数据和该数据的.NET对象表示之间进行转换。

(有趣的是,尽管如此,EF 7可能会更容易使ORM抽象“更纯粹”,甚至提供适合在自动化测试中使用的内存提供程序。)

Context_Info()为null的原因是因为存储过程在您调用它们之后立即执行。 执行dataContext.SaveChanges()时不会调用它们。 您要做的是在与dataContext.SaveChanges()相同的事务中调用存储过程。 为了做到这一点,这就是你的代码应该是这样的。

 public partial class MyDbContext : DbContext { //... public virtual int SetUserContext(string modifierId) { return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("SetUserContext", modifierId); } } public class UserService { private MyDbContext m_dataContext; public UserService(MyDbContext dataContext) { m_dataContext = dataContext; { public User CreateUser(string firstName, string lastName, DateTime birthDate, int modifiedId) // list other parameters here { using (var transaction = new TransactionScope()) // this creates a new transaction { m_dataContext.Users.Add(new User() { //... }); // instead of passing modified id into save changes, you can just call your stored procedure here m_dataContext.SetUserContext(modifiedId); // and then call the regular save changes m_dataContext.SaveChanges(); transaction.Complete(); // this commits the transaction } } } 

注意此处介绍的最终解决方案的体系结构目前并不是那么好。 我建议实现Repository模式,而不是让服务有权访问数据上下文。