在执行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;
}

在执行Entity Framework . savechanges (overload)时丢失了CONTEXT_INFO

根据CONTEXT_INFO文档,

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

"会话或批处理"或多或少对应于. net托管连接。在这里,了解一点EF连接管理的知识会有所帮助。

EF的默认行为是相当自由地打开和关闭数据库连接,因为. net连接池使这种操作相当有效。在您的情况下,这意味着您的初始存储过程调用发生在与后续EF保存操作不同的"会话或批处理"中。 <标题>修复h1> 很容易修复:您只需要显式地控制数据库连接。您可以通过为上下文对象提供构造函数重载来实现这一点,该构造函数重载提供到基DbContext类的打开连接,或者在调用存储过程之前手动打开连接。

这样可以吗?

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

使用EntityFramework的全部意义在于避免管理SQL连接。我觉得什么地方不对劲。

将EF代码从底层实现中抽象出来通常是不现实的。我甚至不确定它是否特别值得。这种抽象通常在存储库/工作单元层中更好。

在我看来,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访问数据上下文。