【问题标题】:MVC 3/EF repository pattern and proper data accessMVC 3/EF 存储库模式和正确的数据访问
【发布时间】:2012-02-16 13:25:25
【问题描述】:

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

我找到了这篇文章,此时我感到相当困惑,所以我想知道是否有任何理由尝试使用断开连接的 EF 以及您为什么要这样做:http://www.codeproject.com/Articles/81543/Finally-Entity-Framework-working-in-fully-disconne?msg=3717432#xx3717432xx

所以总结一下我的问题:

  • 下面的代码可以接受吗?
  • 它应该适用于大型 MVC 应用程序吗?
  • 有没有更好的方法?
  • 是否会从 EF 保持打开与 SQL 的不必要连接? (SQL Profiler 让它看起来像是在 using 语句退出后仍保持打开状态)
  • 断开连接的框架想法是否更好,您为什么还要这样做?我认为我们不需要跨层跟踪数据...

注意: 存储库实现 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);
        } 
    }

存储库片段(构造函数、Dispose 等):

    public class AccountRepository : IDisposable
    {
         private DbContext entities;

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

         ...

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

【问题讨论】:

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


    【解决方案1】:

    可接受的内容是相当主观的,但如果您想进行适当的数据访问,我建议您使用存储库模式,因为随着您的应用程序变得越来越复杂,它会崩溃。

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

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

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

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

    现在这意味着您有 2 个数据库调用,而实际上应该只是一个。随着您的屏幕变得越来越复杂,这将导致数据库点击次数越来越多,如果您的应用程序获得大量流量,这将导致性能问题。在存储库模式中,唯一真正做到这一点的方法是向存储库添加特殊方法来执行特定查询,例如:

    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 需求和用户使用场景,那么您的应用程序将无法很好地扩展并且不会具有高性能。有时,如果您不打算让您的网站变大,这不是问题,但记住这些事情永远不会有坏处。

    【讨论】:

    • 感谢 KallDrexx 的精彩帖子。您是否可以提供有关如何管理单个查询和命令类的示例或链接?
    • 我个人使用的界面类似于我在scatteredcode.wordpress.com/2011/08/13/… 中的概述(我从那篇文章中对其进行了一些改进,但这是基本思想)。 Ayende 在ayende.com/blog/3955/repository-is-the-new-singleton 上有一篇关于查询对象的好帖子。归根结底,就是要尝试找到让您感觉最舒服的方式,而这只能通过尝试不同的方法并找出最适合您的方式来实现。
    • 另外我要补充一点,我学到的一件事是,在架构模式设计方面,每个人都将自己的偏好作为福音宣扬,但任何事情都不应被视为如此。每个人都说他们的方式“是最好的”(tm),但没有 100% 的方式来做到这一点(每种模式都有其缺陷),这真的取决于你找出最适合你、你的产品和你的团队(如果有的话)
    • 断开的层意味着我不必仅仅因为我需要重新组织我的数据访问而更改 MVC 控制器代码。例如,如果我注意到我的应用程序中有 12 个区域需要关于用户问题的不同查询,我可能会将这些查询方法中的每一个移出 UserQueries 类并移入 UserQuestionQueries 类。使用断开连接的模型,它允许我更改分组,并且我的 MVC 控制器代码仍然可以完美运行而无需修改。
    • 不用担心 :)。如果您刚刚开始,我建议您开始使用简单的方法并将您的 EF 代码直接放在您的 MVC 控制器中。这将帮助您以最快的速度学习 MVC 和 EF,一旦您掌握了窍门,您就可以扩展并尝试不同的数据层架构。那将是我个人的方法。
    【解决方案2】:

    无论您做什么,您都可以考虑像这样将实例化并将上下文处理到您的控制器:

    public class MyController : Controller
    {
        private Entities context = new Entities();
    
        ...
    
        public override void Dispose()
        {
            context.Dispose();
        }
    }
    

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

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

    repository.Users.GetUser(userName);
    

    在这种情况下,“用户”是用户存储库类的延迟加载实例,它重用存储库中的上下文。因此,您的存储库中该 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 实例化并填充您的模型,那么您所要做的就是附加和保存。如果您发现可以重用代码,请将其移至可以重用的地方。如果您的应用程序开始变得过于复杂,请尝试其中的一些建议,直到找到适合您的方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-05
      • 1970-01-01
      相关资源
      最近更新 更多