解决方案:
如您所见:在这里
如果操作之前对象上下文尚未打开,则它将打开该连接。如果对象上下文在操作期间打开连接,则在操作完成后它将始终关闭连接。 如果您手动打开连接,则对象上下文不会关闭它。调用“关闭”或“处置”将关闭连接。
问题在于EF将打开和关闭SetUserContext的连接,因此我将松散CONTEXT_INFO。为了保持它,我需要手动打开连接,并在SaveChanges之后关闭它
public int SaveChanges(string modifierId) { Database.Connection.Open(); SetUserContext(modifierId); var changes = base.SaveChanges(); Database.Connection.Close(); return changes; }
问题 :
该系统在数据仓库上工作。数据库必须知道谁对其进行了修改,并将所有更改保存在“审核”表中。
为了达到这个结果,我主要依靠触发器和过程:
此函数 将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 request进行测试时,一切似乎都正常运行,并且所有工作正常。问题是我需要使用实体框架在WCF服务中调用它们。现在这就是麻烦开始的地方。我使用重载方法 通过实体设置了CONTEXT_INFO :
public int SaveChanges(string modifierId) { SetUserContext(modifierId); return base.SaveChanges(); }
但是当base.SaveChanges(); 接到电话,我得到:
无法将值NULL插入表’dbo.Audit_Users’的列’ModifierId’中;列不允许为空。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<Address> Addresses { get; set; } public virtual DbSet<Contact> Contacts { get; set; } public virtual DbSet<Email> Emails { get; set; } public virtual DbSet<File> Files { get; set; } public virtual DbSet<StatusUser> StatusUsers { get; set; } public virtual DbSet<TypeCommon> TypeCommons { get; set; } public virtual DbSet<TypeFile> TypeFiles { get; set; } public virtual DbSet<TypeUser> TypeUsers { get; set; } public virtual DbSet<User> Users { get; set; } public virtual DbSet<Workflow> 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<User>(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<UserDto>(user); userToReturn.Type = type; userToReturn.Status = "Created"; transaction.Complete(); } } } return userToReturn; }
根据文档 CONTEXT_INFO,
CONTEXT_INFO
返回使用SET CONTEXT_INFO语句为当前会话或批处理设置的context_info值。
“会话或批处理”或多或少与.NET托管连接相对应。在这里,对EF连接管理有所了解会有所帮助。
EF的默认行为是相当自由地打开和关闭数据库连接,因为.NET连接池使此连接相当有效。在您的情况下,这意味着您的初始存储过程调用与后续的EF保存操作以不同的“会话或批处理”发生。
这很容易解决:您只需要对数据库连接进行显式控制。您可以通过为上下文对象提供一个向基DbContext类提供打开连接的构造函数重载,或者通过在存储过程调用之前手动打开该连接来实现此目的。
DbContext
[您能建议一个更漂亮的方法吗?
和
使用EntityFramework的全部目的是避免必须管理SQL连接。我觉得某处有问题。
将EF代码从底层实现中抽象出来通常是不现实的。我不确定这是否特别理想。通常,使用工作层的存储库/单元可以更好地实现这种抽象。
恕我直言,EF的“重点”是避免在数据库的原始数据和该数据的.NET对象表示之间进行大量的样板代码转换。
(不过,有趣的是,EF 7可能会更容易保持ORM抽象的“纯净”,甚至提供适用于自动化测试的内存提供程序。)