MVC 3 / EF / SQL处理连接,处置和超时

时间:2012-02-15 14:29:37

标签: sql asp.net-mvc exception entity-framework-4.1

目前,这就是我在MVC 3应用程序中处理数据的方式。作为MVC 3和实体框架的新手,我不太确定这是在应用程序中处理数据的最佳方法。事实上,下面检查UserExists的调用有时会产生SQLConnectionTimeout问题,这似乎是完全随机的。我试过通过SQL分析器跟踪问题,看起来超时发生在从EF建立连接之后 - > SQL。

我以为我已经在另一个问题上解决了这个问题,但是它又重新出现了,所以我想让大家看看下面是否是在我的应用程序中尝试数据处理的最佳方式,或者是否有更好的方法这可能会解决超时问题。

以下是其他文章的链接,如果它有帮助:MVC 3/EF/SQL Server strange connection timeout issue

总结一下我的问题:

  • 以下代码是否可以接受?
  • 它应该运作正常吗?
  • 有更好的方法吗?
  • 从EF开始,SQL的不必要连接是否仍然打开? (即使在using语句退出后,SQL Profiler也会使它看起来保持打开状态)
  • 我在其他文章中发布了关于超时问题的任何想法吗?

注意:存储库实现IDisposable并具有下面列出的dispose方法。它在存储库构造函数中创建实体上下文的新实例。

控制器(使用自定义成员资格提供程序登录):

if (MembershipService.ValidateUser(model.UserName, model.Password))
{
    User newUser = new User();                    

    using (AccountRepository repo = new AccountRepository())
    {
         newUser = repo.GetUser(model.UserName);
         ...
    }
}

会员提供商ValidateUser:

public override bool ValidateUser(string username, string password)
{
    using (AccountRepository repo = new AccountRepository())
    {
        try
        {
            if (string.IsNullOrEmpty(password.Trim()) || string.IsNullOrEmpty(username.Trim()))
                return false;

            string hash = FormsAuthentication.HashPasswordForStoringInConfigFile(password.Trim(), "md5");

            bool exists = false;

            exists = repo.UserExists(username, hash);

            return exists;
        }catch{
            return false;
        }
    }
}

GetUser的帐户存储库方法& UserExists:

获取用户:

public User GetUser(string userName)
    {
        try
        {
            return entities.Users.SingleOrDefault(user => user.UserName == userName);
        }
        catch (Exception Ex)
        {
            throw new Exception("An error occurred: " + Ex.Message);
        }           
    }

用户存在:

 public bool UserExists(string userName, string userPassword)
 {
        if (userName == "" || userPassword == "")
            throw new ArgumentException(InvalidUsernamePassword);

        try
        {
            bool exists = (entities.Users.SingleOrDefault(u => u.UserName == userName && u.Password == userPassword) != null);
            return exists;
        }
        catch (Exception Ex)
        {
            throw new Exception("An error occurred: " + Ex.Message);
        } 
    }

Repository Snippets(构造函数,Dispose等):

    public class AccountRepository : IDisposable
    {
         private DbContext entities;

         public AccountRepository()
         {
            entities = new DbContext();
         }

         ...

         public void Dispose()
         {
            entities.Dispose();
         }
    }

谢谢大家 - 我意识到这个问题会让你因为巨大的文字墙而超过9000!

1 个答案:

答案 0 :(得分:0)

我们通常遵循使用IActionFilter控制实例化和处理上下文的模式,并提供一种机制将其注入依赖类(使用Ninject)。

如果您没有使用依赖注入/ IoC,您可以使用基本控制器,如下所示:

using System;
using System.Diagnostics;
using System.Linq;
using System.Transactions;
using System.Web.Mvc;

public class ControllerBase : Controller
{
    private ContextState contextState;

    protected EntityContext Context
    {
        get { return this.contextState.Context; }
    }

    protected TransactionScope TransactionScope
    {
        get { return this.contextState.TransactionScope; }
    }

    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        IsolationLevel isolationLevel = filterContext.ActionDescriptor
            .GetCustomAttributes(typeof(UnitOfWorkAttribute), false)
            .Cast<UnitOfWorkAttribute>()
            .Select(a => a.IsolationLevel)
            .DefaultIfEmpty(IsolationLevel.ReadCommitted)
            .First();

        Trace.TraceInformation("Creating database context & transaction scope with isolation {0}.", isolationLevel);

        this.contextState = new ContextState
            {
                TransactionScope = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions { IsolationLevel = isolationLevel }),
                Context = new EntityContext()
            };
    }

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);

        try
        {
            if (filterContext.Exception == null)
            {
                Trace.TraceInformation("Commiting transaction scope.");
                this.contextState.TransactionScope.Complete();
            }
            else
            {
                Trace.TraceInformation("Rolling back transaction scope.");
            }
        }
        finally
        {
            try
            {
                Trace.TraceInformation("Disposing database context.");
                this.contextState.Context.Dispose();
            }
            catch (Exception e)
            {
                Trace.TraceError("Failed to dispose database context. {0}", e);
            }

            try
            {
                Trace.TraceInformation("Disposing transaction scope.");
                this.contextState.TransactionScope.Dispose();
            }
            catch (Exception e)
            {
                Trace.TraceError("Failed to dispose transaction scope. {0}", e);
            }

            this.contextState = null;
        }
    }

    private class ContextState
    {
        public EntityContext Context { get; set; }
        public TransactionScope TransactionScope { get; set; }
    }
}

/// <summary>
/// Marks an MVC action as requiring a particular <see cref="IsolationLevel" /> when a transaction is
/// created for it.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class UnitOfWorkAttribute : Attribute
{
    private readonly IsolationLevel isolationLevel;

    public UnitOfWorkAttribute(IsolationLevel isolationLevel)
    {
        this.isolationLevel = isolationLevel;
    }

    /// <summary>
    /// Gets an <see cref="IsolationLevel" /> value indicating the isolation level
    /// a transaction should use.
    /// </summary>
    public IsolationLevel IsolationLevel
    {
        get
        {
            return this.isolationLevel;
        }
    }
}

这里我们在操作执行之前创建一个上下文实例和一个事务范围,然后在操作完成后进行清理。

在派生控制器中,您可以执行以下操作...

public class HomeController : ControllerBase
{
    public ActionResult Index()
    {
        using (AccountRepository accountRepository = new AccountRepository(this.Context))
        {
            // do stuff
        }

        return View();
    }
}

将上下文传递到您的存储库有点混乱,可以使用像Ninject之类的东西来整理注入依赖项,而不是提供它。如果您有兴趣,http://stevescodingblog.co.uk/dependency-injection-beginners-guide/提供了一个非常合理的起点。

您还可以使用UnitOfWorkAttribute标记操作,以控制上下文使用的事务的创建。建议在执行数据库工作时不要使用隐式事务(http://nhprof.com/Learn/Alerts/DoNotUseImplicitTransactions),因此我们总是在执行操作时创建事务范围。这几乎没有开销,因为除非打开连接,否则事务范围不会很大。

编辑:只是回答你的另一个问题......

与SQL的不必要连接是否仍然可以从EF打开? (即使在using语句退出后,SQL事件探查器也会让它保持打开状态)

这里最有可能的原因是连接池。 ADO.NET将在一段时间内保持开放连接,这使得后续调用更加高效,因为您没有打开连接的延迟。

干杯,

迪安