MVC 3 / EF存储库模式和正确的数据访问

时间:2012-02-16 13:25:25

标签: sql asp.net-mvc repository disconnected-environment

作为MVC 3和EF的新手,我正在努力了解为我的公司开发应用程序的最佳架构方法。该应用程序将是一个大型应用程序,可能同时处理数百个用户,所以我想确保我理解并遵循正确的程序。到目前为止,我已经确定一个简单的存储库模式(例如Controller - > Repository - > EF)方法是最好和最容易实现的,但我不确定这是否绝对是最好的做事方式。应用程序基本上会返回在devexpress网格中向用户显示的数据,并且可以修改此数据/添加到其中等。

我发现这篇文章对我来说相当混乱,所以我想知道是否有任何理由尝试使用断开连接的EF以及为什么你甚至想要这样做:{{3} }

总结一下我的问题:

  • 以下代码是否可以接受?
  • 它是否适用于大型MVC应用程序?
  • 有更好的方法吗?
  • 从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();
         }
    }

2 个答案:

答案 0 :(得分:5)

什么是可接受的是非常主观的,但如果你想进行正确的数据访问,我建议你使用存储库模式,因为它会因你的应用程序变得更复杂而崩溃。

最大的原因是最小化数据库访问。例如,查看您的存储库并注意GetUser()方法。现在从代码中退一步,考虑一下如何使用您的应用程序。现在考虑一下您在没有任何其他数据的情况下从用户表中请求数据的频率。除非您正在创建基本数据输入应用程序,否则答案几乎总是“很少”。

你说你的应用程序会显示很多网格。该网格中有哪些数据?我假设(不知道您的应用程序域)网格将用户数据与其他与该用户相关的信息组合在一起。如果是这种情况,您如何使用您的存储库?

一种方法是单独调用每个存储库的方法,如下所示:

var user = userRepository.GetUser("KallDrexx");
var companies = companyRepository.GetCompaniesForUser(user.Id);

现在这意味着您有两个数据库调用,它们应该只是一个。随着屏幕变得越来越复杂,这将导致数据库命中数量增加和增加,如果您的应用程序获得大量流量,则会导致性能问题。在存储库模式中执行此操作的唯一真正方法是向存储库添加特殊方法以执行特定查询,例如:

public class UserRepository
{
    public User GetUser(string userName)
    {
        // GetUser code          
    }

    public User GetUserWithCompanies(string userName)
    {
        // query code here
    }
}

现在,如果您需要用户并在一个查询中说出他们的联系人数据,会发生什么。现在,您必须向用户存储库添加另一个方法。现在假设您需要执行另一个查询,该查询还返回每个公司拥有的客户端数量,因此您需要添加另一个方法(或添加可选参数)。现在假设您要添加一个返回所有公司及其包含的用户的查询。现在你需要一个新的查询方法,但接下来的问题是你把它放在User存储库或Company存储库中吗?如何跟踪它所处的位置,并在以后需要时在GetUserWithCompanyGetCompanyWithUsers之间轻松选择?

从那时起,一切都变得非常复杂,而那些使我放弃了存储库模式的情况。我现在做的数据访问是我创建单独的查询和命令类,每个类代表1(并且只有1)查询或数据更新命令到数据库。每个查询类都返回一个视图模型,该模型仅包含一个特定用户使用场景所需的数据。还有其他数据访问模式也可以工作(规范模式,一些优秀的开发人员甚至说你应该只在控制器中进行数据访问,因为EF 是你的数据访问层)。

成功进行数据访问的关键是良好的规划。你知道你的屏幕会是什么样子吗?你知道用户将如何使用你的系统吗?您是否知道每个屏幕上实际存在的所有数据?如果对这些问题的答案都是肯定的,那么你需要退后一步,忘掉数据层,因为数据层是(或应该是一个好的应用程序),根据应用程序的实际情况确定使用时,UI和屏幕不应该取决于数据层的设计方式。如果在开发数据访问时没有考虑UI需求和用户使用场景,那么您的应用程序将无法很好地扩展并且不会具有高性能。如果你没有计划在你的网站上做大事,那么有时候这不是问题,但是记住这些事情永远不会伤害。

答案 1 :(得分:1)

无论你做什么,你都可以考虑移动实例化并将你的上下文处理到你的控制器,如下所示:

public class MyController : Controller
{
    private Entities context = new Entities();

    ...

    public override void Dispose()
    {
        context.Dispose();
    }
}

然后,您可以将该上下文传递给任何需要它的方法,而不会重复创建它的开销。

我不同意存储库模式出于同样的原因必然是坏的。您可以创建多个类来分解代码,使其可管理并仍然重用相同的上下文。这可能看起来像这样:

repository.Users.GetUser(userName);

在这种情况下,“Users”是一个延迟加载的用户存储库类实例,它重用了存储库中的上下文。因此,存储库中Users属性的代码如下所示:

private UserRepository users;

public UserRepository Users
{
    get
    {
        If (users == null)
        {
            users = new UserRepository(this);
        }
        return users;
    }
}

然后,您可以通过属性将您的上下文公开给这些其他延迟加载的类。

我认为这不一定与KallDrexx的模式相冲突。他的方法简单地翻转了这个而不是

repository.Users.GetUser(userName);

你会有像

这样的东西
UserQuery query = new UserQuery(repository.Users);

这就成了语法问题。你想要这个:

repository.Area.Query(value1, value2, ...);

或者这个:

AreaQuery query = new AreaQuery { Property1 = value1, ... };

后者实际上对模型绑定更有效,但实际上当你需要编码它时显然更加冗长。

KallDrexx给出的最佳建议是将你的代码放在我的行动中,然后弄明白。如果您正在进行简单的CRUD,那么让MVC实例化并填充您的模型,然后您需要做的就是附加并保存。如果您发现可以重用代码,请将其移动到可以重用的位置。如果您的应用程序开始变得过于复杂,请尝试其中一些建议,直到找到适合您的方式。