覆盖EntityFramework的SaveChanges方法 - 获取SQL并调用自定义存储过程

6
我正在寻找一种方法来覆盖EF中的SaveChanges方法/进程。我们需要以某种方式获取SQL,防止正常的更新/删除/插入执行,并使用生成的SQL运行我们的自定义过程。
  • 像往常一样调用 SaveChanges()。让EF生成SQL。
  • 获取SQL
  • 防止以正常方式执行该SQL
  • 调用自定义存储过程(带有其他参数等)
  • 假装我们执行了 SaveChanges(或只返回0)

我唯一真正看到的问题是从 SaveChanges方法内部获取SQL。理想情况下,我们将采取以下措施...

  1. 获取提供程序/连接/等
  2. 设置事件钩子以处理此问题
  3. 完成,无需更改/覆盖等

我们正在使用MVC4&EF5针对三个字母缩写的数据库。这里的重点是避免在每个更新操作中手动编写SQL,并依赖于EF为我们生成所有内容。由于该过程需要直接SQL

是的,这不是一个好方法(单个过程),但我们在这件事上没有选择。根本没有选择。如果我们不能这样做,那么我们将需要编写自定义SQL。也许还有另一种方法可以强制执行此操作,在其中传递上下文并自己完成工作?然后我们只需审计永远不调用'SaveChanges()' :D

解决方案


我使用EFTracingProvider作为创建自己的提供程序的起点,该提供程序执行此操作(以及其他操作)。您也可以仅使用EFTracingProvider将所有内容放置在实体类中并处理事件。您将看不到修改后的SQL,因为此事件将在其后触发,因此需要进行自己的日志记录。这已被简化以更好地适应网站:)

public class MyEntities : MyBaseEntities
{

    public MyEntities(): this(connectionString: "name=MyBaseEntities") {}
    public MyEntities(string connectionString) 
             : base(MakeConnection(connectionString, "EFTracingProvider")) {}

    /// <summary>
    /// Insert the wrapped connection by calling the base toolkit.
    private static EntityConnection MakeConnection(string connectionString, params string[] providers)
    {
        var conn = EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(
            connectionString,
            providers
            );

        //get the tracing connection, so that we can attach event handlers
        var us = conn.UnwrapConnection<EFTracingConnection>();
        if (us != null)
        {
            us.CommandExecuting += BeforeExecute;
        }
        return conn;
    }

    private static void BeforeExecute(object sender, CommandExecutionEventArgs e)
    {
        // If an Create/Update/Delete action then we need to wrap it in our custom proc   
        if (IsCudAction(e.CommandTree))
        {
            var text = cmd.Parameters.Cast<DbParameter>().Aggregate(
                cmd.CommandText, 
                (current, p) => current.Replace(p.ParameterName, SafeSql.Prepare(p.Value)));

            cmd.CommandType = CommandType.StoredProcedure;
            cmd.CommandText = "[dbo].[ExecuteForMe]";
            cmd.Parameters.Clear();
            cmd.Parameters.AddRange(new[]
                {
                    new SqlParameter("commandText", text),
                    new SqlParameter("extraInfo", "logging context")
                });
        }
    }

    public static bool IsCudAction(DbCommandTree commandTree)
    {
        if (commandTree is DbUpdateCommandTree) return true;
        if (commandTree is DbDeleteCommandTree) return true;
        if (commandTree is DbInsertCommandTree) return true;
        if (commandTree is DbQueryCommandTree) return false;
        if (commandTree is DbFunctionCommandTree) return false;
        throw new InvalidOperationException("Unknown type of CommandTree: " + commandTree.GetType().Name);
    }
}

4
您知道吗,您可以将CRUD操作映射到存储过程中,并使用它们调用您的中央存储过程。 - Gert Arnold
@GertArnold 这似乎是值得发布为答案的东西。无论如何,我会点赞的... - Tieson T.
我听说过这个(但没有使用过)。标准做法(这是一个大型且稍微有趣的公司)是使用一个存储过程,将其放入所有数据库中。我们甚至不被允许编写存储过程(而且我也不想为 ~70 个表编写 3 个存储过程,然后手动映射它们,还要维护它们)。如果 EF 能够 生成 这些存储过程!即使它可能对我不适用(因为公司限制),您也应该将其添加为答案。 - Andrew
1
考虑到操作失败时会发生什么,除非你通过过程将故障冒泡出来,否则dbcontext的更改跟踪将会出现问题,特别是在执行需要事务管理的多个操作时。 - Paul Zahra
对于任何感兴趣的人:这是一个基本的审计过程。事实上,我们将插入到“temp”表中,这些表具有用于审计、日志记录/错误报告的额外列,然后调用单独的过程来运行事务。这是一个两步的过程。我们仍然需要一些创造性的代码将对象映射到新的“temp”表,因为它们作为EF的一部分没有被映射,但是我们可以重复使用元数据,而且所有命名、模式、过程参数等都是一致的。 - Andrew
显示剩余3条评论
2个回答

2

很酷。我之前安装过它(还有一个审计插件),但还没有使用过。我没有深入研究过EF,通常使用其他框架。如果没有什么,这是一个非常好的地方寻找钩子,也许是更多自定义内容的起点 :) - Andrew
这给了我一个很好的起点,所以我会给出答案。正如解决方案所示,现在有一个可以获取和修改命令的地方。虽然我不能/没有改变CommandTree类型,但它似乎在SQL Server上仍然有效。现在,我所有的CUD操作都通过另一个过程进行(还有sp_executeSql,呃)。 - Andrew
很高兴它有所帮助。起初,通过一个存储过程似乎是一个有点糟糕的解决方案,老实说,我羡慕目前正在使用SP、标量函数、视图、触发器、强类型数据集、内联SQL和现在的EF4系统,也就是一团糟! - Paul Zahra
我希望我能使用所有这些!这是DB2,所以...呃。谈论一下使用反生产力的数据库。 - Andrew
数一数你的福气,我们刚刚清理了 Pervasive - 那真是一堆垃圾。 - Paul Zahra

2
正如我在评论中所说的,你可以将 map crud actions to stored procedures(即创建、更新和删除操作)映射到存储过程中。是的,编写、映射和维护这些存储过程需要大量的工作,但可能带来更好的性能和安全性。这就是为什么数据库管理员非常喜欢它们的原因。甚至可能是你的数据库中创建这个中心单体程序的主要原因。可能有一种使用“CUD存储过程”来满足相同要求的方法。

是的,它可以工作 :) 上次我使用了一个工具来避免这个问题,该工具包括编写存储过程在内的所有操作。每个区域都有自己的模型,只包含他们特别使用的字段等。即使有一个庞大的模型,这也不是太糟糕的事情。当然,共同的东西是共同的。 - Andrew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接