我有一个 WCF 数据服务,并且我打算在插入或插入时使用一些基于 session 的表函数(创建可在当前 session 中使用的临时表) 更新。
我尝试使用 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)。
这是否意味着我无法在 session 中使用 EF?或者每个 EF 函数都使用自己的上下文? 由于 session 表被其他触发器使用,因此我需要有正确的键值。
对此我该怎么办? EF 是否能够使用这些类型的功能有任何提示吗?
更新:
我了解到 EF 在每个执行命令之前使用 exec sp_reset_connection
,并且它重置所有临时变量和表。
因此我尝试创建一个事务来强制 EF 在一个 session 中执行命令:
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();
}
它仍然会创建新 session ,因此我无法真正合并这两个命令。
有什么建议吗?
最佳答案
EF 将为每个命令打开和关闭 SqlConnection
(导致连接重置),除非
- 显式打开连接并将其传递给
DbContext
构造函数, - 调用
DbContext.Database.Connection.Open()
,或 - 使用事务,这将导致连接池每次都返回相同的连接。
编辑:
看起来,当连接从隔离连接池中 check out 时,TransactionScope 不会抑制连接重置。因此,使用 TransactionScope
时,您仍然需要显式打开 DbContext.Database.Connection
才能在命令之间使用 session 状态。
但是 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();
}
}
}
关于sql - 如何在 Entity Framework 5 中使用基于 SQL session 的表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44749315/