如何在 Entity Framework 5 中使用基于 SQL 会话的表

Posted

技术标签:

【中文标题】如何在 Entity Framework 5 中使用基于 SQL 会话的表【英文标题】:How to use SQL session-based tables with Entity Framework 5 【发布时间】:2017-11-28 16:18:34 【问题描述】:

我有一个 WCF 数据服务,我打算在 插入 时使用一些基于会话的表函数(创建可在当前会话中使用的临时表)或更新

我尝试像这样使用SaveChanges 方法:

public partial class MyContext: DbContext

    public override int SaveChanges()
              
        var res = SetValues(true);
        var s = Database.SqlQuery<string>("SELECT [Key] FROM  TempContextView").ToList();
        System.IO.File.AppendAllText(@"c:\Temp\session.txt", $"SIZE S: s.Count, script res: res");
        foreach (var element in s)
                    
            System.IO.File.AppendAllText(@"c:\Temp\session.txt", $"RES: element"); //never reached
        
        return base.SaveChanges();
    

    public int SetValues(bool insert)
    
        System.IO.File.AppendAllText(@"c:\Temp\session.txt", "SetV: " + insert);
        return Database.ExecuteSqlCommand(insert ? "INSERT INTO TempContextView ([Key],[Value]) VALUES('Flag', '1')" : "DELETE FROM TempContextView WHERE[Key] = 'Flag'");
    

TempContextView 是一个view,它提供了一个由函数创建的临时表

SELECT  TOP (32) [Key], Value
FROM    Schema1.getContextTable()
ORDER BY [Key]

function [Schema1].[getContextTable]()
  RETURNS @Context TABLE([Key] varchar(126), [Value] varchar(126))
  WITH SCHEMABINDING
as...

但是,当我从函数创建的表中选择值时,它什么也不返回(查询大小为 0,而插入返回 1)。

这是否意味着我不能在会话中使用 EF?还是每个 EF 函数都使用自己的上下文? 由于会话表被其他触发器使用,我需要有正确的键值。

我该怎么办?如果 EF 能够使用这些类型的功能,有什么提示吗?

更新:

我了解到 EF 在每个执行命令之前使用exec sp_reset_connection,它重置所有临时变量和表

所以我尝试创建一个事务来强制 EF 在一个会话中执行命令:

  using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew))
  
    Database.ExecuteSqlCommand("INSERT INTO TempContextView ([Key],[Value]) VALUES('Flag', '1')"); //session #1?
    base.SaveChanges(); //session #2? :(
    scope.Complete();
  

它仍然会创建新会话,因此我无法真正合并这两个命令。

有什么建议吗?

【问题讨论】:

“基于会话的表函数”是什么意思? @DavidBrowne-Microsoft 我的意思是上下文,因为它使用上下文一段时间(会话)。我是不是措辞不好? 这可能与事务有关,可能表不会更新,因为你在同一个事务中。尝试在没有事务的情况下运行命令 - ***.com/questions/36609208/… @ZivWeissman 您的建议看起来很有希望,但我使用的是 Entity Framework 5,它没有事务修饰符作为第一个参数。有其他选择吗? @Nestor 嗯,我认为在 ef5 上没有 ExecuteSqlCommand 的事务,但您可以尝试使用纯 ADO.NET? 【参考方案1】:

EF 将为每个命令打开和关闭 SqlConnection(导致连接重置),除非

    显式打开连接并将其传递给 DbContext 构造函数, 调用 DbContext.Database.Connection.Open(),或 使用事务,这将导致连接池每次返回相同的连接。

当连接从隔离连接池中签出时,看起来 TransactionScope 不会抑制连接重置。因此,使用 TransactionScope,您仍然必须显式打开 DbContext.Database.Connection 才能在命令之间使用会话状态。

但 DbContext.Database.BeginTransaction() 有效(可能是通过在 DbContext 的生命周期内阻止连接池)。

这是一个使用 sp_set_sesson_context 的完整工作示例:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;

namespace ConsoleApp6



    [Table("Customers")]
    public class Customer
    
        public int CustomerID  get; set; 
        public string Name  get; set; 

        [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
        public string UpdatedBy  get; set; 
    

    class Db : DbContext
    

        public DbSet<Customer> Customers  get; set; 
    
    class Program
    
        static void Main(string[] args)
        
            Database.SetInitializer(new DropCreateDatabaseAlways<Db>());

            using (var db = new Db())
            
                db.Database.Initialize(false);
                db.Database.ExecuteSqlCommand(@"
create trigger tg_customer on customers after insert, update 
as
begin
   update customers set UpdatedBy = cast(SESSION_CONTEXT(N'user') as varchar(200))
   where CustomerId in (select CustomerId from inserted);
end
");

            

            using (var db = new Db())
            
                using (var tran = db.Database.BeginTransaction())
                
                    db.Database.Log = m => Console.WriteLine(m);

                    db.Database.ExecuteSqlCommand("EXEC sp_set_session_context 'user', 'joe'"); //set session context

                    var c = db.Customers.Create();

                    c.Name = "Fred";

                    db.Customers.Add(c);

                    db.SaveChanges();
                    Console.WriteLine(c.UpdatedBy); //joe
                    tran.Commit();
                
            


            using (var db = new Db())
            
                using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew))
                
                    db.Database.Connection.Open();
                    db.Database.ExecuteSqlCommand("EXEC sp_set_session_context 'user', 'alice'"); //set session context

                    var fred = db.Customers.Where(c => c.Name == "Fred").Single();

                    fred.Name = "Fred Jones";

                    db.SaveChanges();

                    Console.WriteLine(fred.UpdatedBy); //alice
                    scope.Complete();
                
            


            Console.ReadKey();
        
    

【讨论】:

我无法真正对交易产生影响。请检查我更新的初始帖子。 问题是在 Entity Framework 5 中没有 Database.BeginTransaction(),我认为这很关键......这是 Entity Framework 6 的代码吗? 然后使用选项 1),并在 DbContext 中显式打开连接。在 DbContext 关闭之前,它将保持打开状态。

以上是关于如何在 Entity Framework 5 中使用基于 SQL 会话的表的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Entity Framework 5 中使用 SQL Server OFFSET 和 FETCH FIRST?

如何在 ASP.NET 5 中将 Entity Framework 6 与 MySQL 一起使用?

如何关闭 Entity Framework Core 5 中的所有约定

如何在MVC 5的类库项目中使用Entity Framework 6 Code First

如何使用 Entity Framework ASP.Net MVC 5 删除多条记录? [关闭]

如何在 ASP.NET MVC 5、Entity Framework 6 中使用流利的 API 映射表?