执行实体框架时,CONTEXT_INFO丢失.SaveChanges(重载)

Lom*_*ani 5 .net c# sql wcf entity-framework

解:

你可以找到:这里

  • 如果在操作之前尚未打开连接,则对象上下文将打开该连接.如果对象上下文在操作期间打开连接,则它将始终在操作完成时关闭连接.
  • 如果手动打开连接,则对象上下文不会关闭它.调用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;            
 }
Run Code Online (Sandbox Code Playgroud)

题 :

该系统适用于数据仓库.数据库必须知道谁修改它并保存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
Run Code Online (Sandbox Code Playgroud)

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

CREATE FUNCTION [dbo].[GetUserContext] ()
RETURNS NVARCHAR (64)
AS
BEGIN
    RETURN CONVERT(NVARCHAR (64), CONTEXT_INFO())
END
Run Code Online (Sandbox Code Playgroud)

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

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)
);
Run Code Online (Sandbox Code Playgroud)

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

 public int SaveChanges(string modifierId)
    {
        SetUserContext(modifierId);
        return base.SaveChanges();
    }
Run Code Online (Sandbox Code Playgroud)

但是当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<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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

创建用户:

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;
}
Run Code Online (Sandbox Code Playgroud)

Oll*_*lly 2

根据文档 CONTEXT_INFO

\n\n
\n

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

\n
\n\n

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

\n\n

默认 EF 行为是相当自由地打开和关闭数据库连接\xe2\x80\x94safe,因为知道 .NET 连接池使其相当高效。在您的情况下,这意味着您的初始存储过程调用发生在与后续 EF 保存操作不同的“会话或批处理”中。

\n\n

修复方法

\n\n

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

\n\n

这个可以吗?

\n\n
\n

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

\n
\n\n

\n\n
\n

使用 EntityFramework 的全部目的是避免管理 SQL 连接。我感觉有什么地方不对劲。

\n
\n\n

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

\n\n

恕我直言,EF 的“重点”是避免在数据库中的原始数据与该数据的 .NET 对象表示形式之间进行大量样板代码转换。

\n\n

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

\n