【问题标题】:Entity Framework 6, Command Interception & Stored ProceduresEntity Framework 6,命令拦截和存储过程
【发布时间】:2019-07-07 04:36:42
【问题描述】:

我被要求为我的工作系统开发审计。该系统已经完成。 我认为 EF 6 的命令拦截应该可以很好地满足我的目的。

但是,在这种情况下,我们想知道是谁发出了请假请求,并且我们希望能够拦截这个插入查询。

using (DataContext context = new DataContext())
    {
      var result = context.CreateLeavePrerequest(
        leaveRequest.LeaveType,
        leaveRequest.StartDate,
        leaveRequest.EndDate,
        leaveRequest.NumberOfDays,
        leaveRequest.EmployeeComment,
        leaveRequest.HasSupportingDocumentation,
        leaveRequest.ResourceTag,
        leaveRequest.RemainingBalance,
        leaveRequest.ApproverResourceTag,
        leaveRequest.CapturerResourceTag,
        leaveRequest.SupportingDocumentID,
        ref id
        );

那么存储过程就是:

CREATE PROCEDURE [dbo].[CreateLeavePrerequest]
(
  @LeaveType VARCHAR(50) ,
  @StartDate DATETIME ,
  @EndDate DATETIME ,
  @NumberOfDays DECIMAL(18, 5) ,
  @EmployeeComment VARCHAR(512) ,
  @SickNoteIndicator BIT ,
  @ResourceTag INT,
  @RemainingBalance DECIMAL,
  @ApproverResourceTag INT,
  @CapturerResourceTag INT,
  @SupportingDocumentID INT,
  @id INT = 0 OUT
)  
AS 
BEGIN
    INSERT  INTO [ESS PER LVE PreRequest]
            ( [Resource Tag] ,
              [Leave Type] ,
              [Start Date] ,
              [End Date] ,
              [No Of Days] ,
              [Employee Comments] ,
              [Sick Note Indicator],
              [Status],
              [Remaining Balance],
              [Approver Resource Tag],
              [Capturer Resource Tag],
              [SupportingDocumentID]
            )
            SELECT  @ResourceTag ,
                    @LeaveType ,
                    @StartDate ,
                    @EndDate ,
                    @NumberOfDays ,
                    @EmployeeComment ,
                    @SickNoteIndicator,
                    'Captured',
                    @RemainingBalance,
                    @ApproverResourceTag,
                    @CapturerResourceTag,
                    @SupportingDocumentID;
SELECT @id
END 

更新:

CreateLeavePrerequest 实现如下:

public ISingleResult<CreateLeavePrerequestResult> CreateLeavePrerequest([global::System.Data.Linq.Mapping.ParameterAttribute(Name="LeaveType", DbType="VarChar(50)")] string leaveType, [global::System.Data.Linq.Mapping.ParameterAttribute(Name="StartDate", DbType="DateTime")] System.Nullable<System.DateTime> startDate, [global::System.Data.Linq.Mapping.ParameterAttribute(Name="EndDate", DbType="DateTime")] System.Nullable<System.DateTime> endDate, [global::System.Data.Linq.Mapping.ParameterAttribute(Name="NumberOfDays", DbType="Decimal(18,5)")] System.Nullable<decimal> numberOfDays, [global::System.Data.Linq.Mapping.ParameterAttribute(Name="EmployeeComment", DbType="VarChar(512)")] string employeeComment, [global::System.Data.Linq.Mapping.ParameterAttribute(Name="SickNoteIndicator", DbType="Bit")] System.Nullable<bool> sickNoteIndicator, [global::System.Data.Linq.Mapping.ParameterAttribute(Name="ResourceTag", DbType="Int")] System.Nullable<int> resourceTag, [global::System.Data.Linq.Mapping.ParameterAttribute(Name="RemainingBalance", DbType="Decimal(18,0)")] System.Nullable<decimal> remainingBalance, [global::System.Data.Linq.Mapping.ParameterAttribute(Name="ApproverResourceTag", DbType="Int")] System.Nullable<int> approverResourceTag, [global::System.Data.Linq.Mapping.ParameterAttribute(Name="CapturerResourceTag", DbType="Int")] System.Nullable<int> capturerResourceTag, [global::System.Data.Linq.Mapping.ParameterAttribute(Name="SupportingDocumentID", DbType="Int")] System.Nullable<int> supportingDocumentID, [global::System.Data.Linq.Mapping.ParameterAttribute(DbType="Int")] ref System.Nullable<int> id)
    {
        IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), leaveType, startDate, endDate, numberOfDays, employeeComment, sickNoteIndicator, resourceTag, remainingBalance, approverResourceTag, capturerResourceTag, supportingDocumentID, id);
        id = ((System.Nullable<int>)(result.GetParameterValue(11)));
        return ((ISingleResult<CreateLeavePrerequestResult>)(result.ReturnValue));
    }

更新 2

在 Global.asax 中注册 DBCommandInterceptor:

 protected void Application_Start()
 {
     DbInterception.Add(new Auditor());
 }

DBCommandInterceptor 实现:

我快速实现了这一点,以便我可以查看是否可以拦截任何内容,因此它只是写入调试窗口。我已经能够截获一些 Select 查询,但这不是我们想要审计的。

 public class Auditor : IDbCommandInterceptor
{
    public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    {
        CreateAuditMessage(command, interceptionContext);
    }

    public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    {
        CreateAuditMessage(command, interceptionContext);
    }

    public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        CreateAuditMessage(command, interceptionContext);
    }

    public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        CreateAuditMessage(command, interceptionContext);
    }

    public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
        CreateAuditMessage(command, interceptionContext);
    }

    public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
        CreateAuditMessage(command, interceptionContext);
    }

    public static void CreateAuditMessage<T>(DbCommand command, DbCommandInterceptionContext<T> interceptionContext)
    {
        string message;

        var parameters = new StringBuilder();
        foreach (DbParameter param in command.Parameters)
        {
            parameters.AppendLine(param.ParameterName + " " + param.DbType + " = " + param.Value);
        }

        if (interceptionContext.Exception == null)
        {
            message = (parameters.ToString() + "  " + command.CommandText);
        }
        else
        {
            message =  (parameters.ToString() + command.CommandText + "  " + interceptionContext.Exception);
        }

        Debug.WriteLine(message);
    }
}

最近,我一直在阅读很多关于实体框架的内容,但我不是很了解。 我已经实现了 IDbCommandInterface 并注册了它等。我可以看到一些其他查询被拦截,但是由于上述情况是存储过程被称为“外部”,所以我无法获取参数。

这是一个简单的例子。并不是所有在系统中以类似方式调用的存储过程都这么简单。

改变上述情况的最佳方法是什么,以便我们可以应用拦截和审计?

【问题讨论】:

  • 请参阅:stackoverflow.com/questions/10555541/… 了解更多信息。
  • @jarlh,Sql Server。
  • @BijayKoirala,您能否向我解释一下您提供的链接与我的问题有何关联?
  • 请说明context.CreateLeavePrerequest()是如何实现的,它的代码是生成的(如果是,使用什么工具)还是手动编程的?
  • @IvanStoev 是的......你是对的。这不是 EF6 代码。我查看了解决方案,有些部分看起来像是使用了 EF6,但大多数似乎是 LINQ-to-SQL。这就是我问这个问题的原因之一,因为即使我对 EF 知之甚少,我也看不出我是如何拦截对数据库的调用的。我的部分困惑是我的经理说他们正在使用 EF6,但我认为我的经理也应该更好地查看代码。 ;-)

标签: c# sql sql-server entity-framework-6


【解决方案1】:

我更喜欢下面的方法来从我的应用程序执行 SQL 存储过程,因为我也使用这种方法从远程服务器执行存储过程。您唯一需要注意的是将正确格式化的参数传递给相应的参数类型。

using (SqlConnection con = new SqlConnection(dc.Con)) {
   using (SqlCommand cmd = new SqlCommand("CreateLeavePrerequest", con)) {
       cmd.CommandType = CommandType.StoredProcedure;

       cmd.Parameters.Add("@LeaveType", SqlDbType.VarChar).Value = leaveType;
       cmd.Parameters.Add("@StartDate", SqlDbType.VarChar).Value = startDate;
       cmd.Parameters.Add("@EndDate", SqlDbType.VarChar).Value = endDate;
       cmd.Parameters.Add("@NumberOfDays", SqlDbType.VarChar).Value = numberOfDays;
       cmd.Parameters.Add("@EmployeeComment", SqlDbType.VarChar).Value = employeeComment;
       cmd.Parameters.Add("@SickNoteIndicator", SqlDbType.VarChar).Value = sickNoteIndicator;
       cmd.Parameters.Add("@ResourceTag", SqlDbType.VarChar).Value = resourceTag;
       cmd.Parameters.Add("@RemainingBalance", SqlDbType.VarChar).Value = remainingBalance;
       cmd.Parameters.Add("@ApproverResourceTag", SqlDbType.VarChar).Value = approverResourceTag;
       cmd.Parameters.Add("@CapturerResourceTag", SqlDbType.VarChar).Value = capturerResourceTag;
       cmd.Parameters.Add("@SupportingDocumentID", SqlDbType.VarChar).Value = supportingDocumentID;
       cmd.Parameters["@id"].Direction = ParameterDirection.Output; 

       con.Open();
       cmd.ExecuteNonQuery();
  }
}

对于任何 NULL 值检查

DBNull.Value

如果这有帮助,请告诉我。谢谢!

【讨论】:

    【解决方案2】:

    您始终可以使用 上下文日志属性 来拦截任何使用 DataContext 触发的数据库查询

    您可以在 DataContext 类上定义构造函数,如下所示。

    public class DataContext : DbContext, IDataContext
    {
    
        public DataContext(string nameOrConnectionString)
            : base(nameOrConnectionString)
        {
              Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
              //NOTE: Instead of Debug.WriteLine, you can stroe it in DB.
    
        }
    .....
    .....
    .....
    }
    

    上下文日志属性记录了什么?

    • 适用于所有不同类型命令的 SQL。例如:

      1- 查询、LINQ 查询、eSQL 查询和原始查询。

      2- 插入、更新和删除生成为 SaveChanges 的一部分

      3- 关系加载查询,例如那些 懒加载生成

    • 参数
    • 命令是否异步执行
    • 指示命令何时开始执行的时间戳
    • 命令是否成功完成,通过抛出失败 一个异常,或者,对于异步,被取消了
    • 结果值的一些指示
    • 执行命令所需的大致时间。笔记 这是从发送命令到得到结果的时间 反对回来。它不包括阅读结果的时间。

    阅读更多。记录到不同的地方,结果记录,格式化等,你可以检查Logging and intercepting database operations

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-02-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多