Entity Framework 6 - ASP.NET 过滤器 - 并发添加

Entity Framework 6 - ASP.NET Filter - Concurrent Adds

我有 Web Api 2 设置 Entity Framework。

我做了一个 ActionFilterAttribute 应该记录每次调用到数据库中。

如果一次调用一个,这会很好用。但是,如果同时进行多个调用,则会失败。对不同类型的并发感到困惑,我会针对我的特定场景提出问题。

如何修改我现有的代码,以便可以添加数据库条目而不会发生并发调用冲突?

附带说明:每个日志条目都有一个应该由数据库生成的 ID。

public class AppLogAttribute : ActionFilterAttribute
{
    //private AppContext db = new AppContext(); //I can use this or the using statement.

    public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        base.OnActionExecuting(actionContext);

        var ip = ((System.Web.HttpContextWrapper)actionContext.Request.Properties["MS_HttpContext"]).Request.UserHostAddress;
        var userId = Convert.ToInt32(actionContext.RequestContext.Principal.Identity.Name);
        var url = actionContext.Request.RequestUri.ToString();
        var date = DateTime.Now;

        var logEntry = new LogEntry
        {
            Date = date,
            IpAddress = ip,
            Url = url,
            UserID = userId
        };

        //Problems here for concurrent calls!
        using (var db = new AppContext())
        {
            db.LogEntries.Add(logEntry);
            db.SaveChanges();
        }
    }
}

日志条目Class ...

public class LogEntry
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long ID { get; set; }
    public int UserID { get; set; }

    public string IpAddress { get; set; }
    public DateTime Date { get; set; }

    public string Url { get; set; }
}

ASP.NET 中的过滤器创建一次并缓存,这就是为什么你不能通过过滤器的构造函数注入你的依赖项,因为它们将是 resolved/injected 一次,所以在你的情况下,如果你声明了上下文作为私有变量,它将是一个单例对象,这就是为什么您遇到并发问题的原因,因为您使用相同的上下文同时添加相同的 2 个请求。

解决您的问题的一个方法是通过 actionContext 参数使用 GetDependencyScope 方法,因此您的代码可以是这样的:

public class AppLogAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        base.OnActionExecuting(actionContext);

        var ip = ((System.Web.HttpContextWrapper)actionContext.Request.Properties["MS_HttpContext"]).Request.UserHostAddress;
        var userId = Convert.ToInt32(actionContext.RequestContext.Principal.Identity.Name);
        var url = actionContext.Request.RequestUri.ToString();
        var date = DateTime.Now;

        var logEntry = new LogEntry
        {
            Date = date,
            IpAddress = ip,
            Url = url,
            UserID = userId
        };

        // get the AppContext from the current scope 
        var appContext = actionContext.Request.GetDependencyScope().GetService(typeof(AppContext)) as AppContext;

        if (appContext != null)
        {
            appContext.LogEntries.Add(logEntry);
            appContext.SaveChanges();
        }
    }
}

请注意,上述解决方案假定您已经在 Api 配置部分将 Ioc 容器设置为依赖项解析器,因此如果您使用的是 Autofac Ioc,则如下所示:

    _container = builder.Build();
    GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(_container);

希望对您有所帮助。