在执行Entity Framework . savechanges (overload)时丢失了CONTEXT_INFO
本文关键字:CONTEXT INFO Entity 执行 Framework savechanges overload | 更新日期: 2023-09-27 18:02:42
解决方案:
如你所见:此处
- 对象上下文将打开尚未打开的连接手术前。如果对象上下文在一个操作,它在操作时总是会关闭连接就完成了。
- 如果您手动打开连接,对象上下文不会关闭它。调用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;
}
问题:
系统工作在一个数据仓库上。数据库必须知道谁修改了它,并将任何更改保存在审计表中。
为了达到这个结果,我主要依靠触发器和和过程:
此函数将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请求和所有这一切都工作。问题是我需要使用实体框架在我的WCF服务中调用它们。这就是麻烦开始的地方。I 通过实体使用重载方法设置CONTEXT_INFO:
public int SaveChanges(string modifierId)
{
SetUserContext(modifierId);
return base.SaveChanges();
}
但是当base.SaveChanges();被调用,我得到:
不能在表的ModifierId列中插入NULL值"dbo.Audit_Users";列不允许为空。插入失败。语句已被终止。
这表明我丢失了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
文档,
返回使用set context_info语句为当前会话或批处理设置的context_info值。
"会话或批处理"或多或少对应于. net托管连接。在这里,了解一点EF连接管理的知识会有所帮助。
EF的默认行为是相当自由地打开和关闭数据库连接,因为. net连接池使这种操作相当有效。在您的情况下,这意味着您的初始存储过程调用发生在与后续EF保存操作不同的"会话或批处理"中。 <标题>修复h1> 很容易修复:您只需要显式地控制数据库连接。您可以通过为上下文对象提供构造函数重载来实现这一点,该构造函数重载提供到基DbContext
类的打开连接,或者在调用存储过程之前手动打开连接。
这样可以吗?
你能建议一个更漂亮的方法吗?
和
将EF代码从底层实现中抽象出来通常是不现实的。我甚至不确定它是否特别值得。这种抽象通常在存储库/工作单元层中更好。使用EntityFramework的全部意义在于避免管理SQL连接。我觉得什么地方不对劲。
在我看来,EF的"全部意义"在于避免在数据库的原始数据和该数据的。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模式,而不是让Service访问数据上下文。