【问题标题】:ASP.Net MVC & Entity Framework project architecture WITHOUT repository pattern没有存储库模式的 ASP.Net MVC 和实体框架项目架构
【发布时间】:2013-10-25 23:40:39
【问题描述】:

我正在使用 ASP.NET MVC 和实体框架开始一个新的小项目。 (SQL Server - 大约 20 个数据库表) 在过去的项目中,我使用过 Linq2SQL,但它似乎已经过时了。

我已经阅读了很多关于使用存储库模式进行 EF 的文章(优点和缺点),对我来说,没有存储库模式的代码似乎更好/更简单。

我创建了以下项目架构:

namespace MySite.Models
{
    public class User
    {
        public Int32 ID { get; set; }
        public String Email { get; set; }
        public String Password { get; set; }
        public String Name { get; set; }
        public Int32 Gender { get; set; }
    }
}

namespace MySite.DAL
{
    public class Users
    {
       public static IEnumerable<User> GetUsers()
        {
            using (var context = new DatingSiteContext())
            {
                return context.Users.ToList();
            }
        }

        public static User GetUserByID(int id)
        {
            using (var context = new DatingSiteContext())
            {
                return context.Users.Find(id);
            }
        }
}

namespace MySite.Controllers
{
    public class HomeController : Controller
    {

        public ActionResult Index()
        {
            ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";

            var users = DAL.Users.GetUsers();

            return View(users);
        }
    }
}
  • 这样使用 EF 有什么缺点? (除了缺少单位 测试支持)
  • 在每次调用 DAL 时创建一个新的 DbContext 是否错误?任何性能命中?
  • 还有其他推荐的使用 EF 的结构吗?例子? :)
  • 您会在新项目中使用 Linq2SQL 吗?

谢谢。

编辑:

GetUsers() 和 GetUserByID() 中的代码只是一个例子,我理解从数据库返回所有记录是一种不好的做法(缺少分页或过滤器)

【问题讨论】:

  • 对我来说,您的 DAL 有点像存储库,只是您添加了名称“Users”而不是“UserRepository”
  • 请记住,像在 return context.Users.ToList(); 中那样调用 ToList() 会将整个数据集返回到内存中,如果有很多用户,这可能会影响性能。
  • 仅供参考,如果 Linq2Sql 适合您,请继续使用它。它不会消失,只是没有获得任何新功能。对于一个小项目,你应该没问题。此外,EF dbcontexts 的生命周期很短,如果您在长时间的情况下使用它们可能会遇到问题,所以是的.. 每次使用时都要创建一个新的。

标签: c# asp.net asp.net-mvc entity-framework linq-to-sql


【解决方案1】:

您实际上只是创建了一个存储库,只是您将其称为“数据访问层”,在我看来,这不是一个好名字,因为实体框架是数据访问层。存储库是数据访问层(在本例中为实体框架)之上的抽象。


在每次调用 DAL 时创建一个新的 DbContext 是错误的吗?任何 性能下降?

不,这很好,但是当您在 DbContext 的一个实例中获取实体并尝试在另一个实例中更新它时,它可能会导致麻烦。


你会在新项目中使用 Linq2SQL 吗?

不,微软提议将实体框架作为 L2SQL 的继任者,并且它的积极开发已经停止。


还有其他推荐的使用 EF 的结构吗?例子? :)

您使用的方法,特定的存储库,将导致大量冗余代码。您可以创建一个实现接口的通用存储库:

public interface IRepository<TEntity> 
    where TEntity : class, new()
{
    IEnumerable<TEntity> GetAll();

    TEntity GetById(int id);

    IQueryable<TEntity> Table { get; }
}

还有一个实现:

public EfRepository<TEntity> : IRepository<TEntity>
    where TEntity : class, new()
{
    private readonly DatingSiteContext _context;

    public EfRepository()
    {
        _context = new DatingSiteContext();
    }

    private IDbSet<TEntity> Entities
    {
        get
        {
            return _context.Set<TEntity>();
        }
    }

    public IEnumerable<TEntity> GetAll()
    {
        return Entities.ToList();
    }

    public TEntity GetById(int id)
    {
        return Entities.Find(id);
    }

    public IQueryable<TEntity> Table 
    { 
        get { return Entities; }
    }   
}

您可以像这样在控制器中使用此存储库:

public class HomeController : Controller
{
    private readonly IRepository<User> _userRepository;

    public HomeController()
    {
        _userRepository = new EfRepository<User>();
    }

    public ActionResult Index()
    {
        var users = _userRepository.GetAll();
        var inactiveUsers = _userRepository.Table.Where(u => !u.Active).ToList();
    }
}

此通用存储库允许您创建模拟存储库:

public class FakeUserRepository : IRepository<User> 
{ 
    // ... 
}


这种方法可能看起来像很多代码,但随着您的实体类型数量的增长,它将为您节省大量工作,因为您所要做的就是在控制器中创建一个IRepository&lt;&gt; 字段。然而,IQueryable&lt;&gt; 属性具有很大的灵活性,它允许延迟执行。

我并不是说这是最好的方法,只是我在项目中经常使用的一种方法。不得不说,我通常在控制器和存储库之间写一个业务(服务)层。我将我的业务逻辑和复杂的 Linq 查询(及其执行)保留在那里。我还使用了一个 IoC 容器来处理我的对象的生命周期(例如 DbContext 和服务的实例)。有关详细信息,请参阅this question

【讨论】:

  • 我会改写。 Repository 是数据访问层之上的抽象,不一定是 ORM。
  • 感谢您的详细回复 :) 1. 您写道“在 DbContext 的一个实例中获取实体并在另一个实例中更新它时可能会造成麻烦。”上面描述的通用存储库是否有助于解决这个问题? 2. 我尝试对存储库使用通用接口,但我发现 90% 的时间我的查询更加具体/复杂,我只是不使用通用 GetByID 或GetAll ,你还会看到创建通用接口的意义吗?3. 在通用类中,你为每个存储库创建了一次 DbContext,这是为什么呢?需要使用 dispose 函数吗?
  • @sharru - 通用存储库和存储库之间存在差异。通用存储库通常是专用存储库的基础。您可以在此基础上进行构建。
  • 我注意到您使用通用接口提供的 Table 函数在控制器本身中编写了额外的 Linq 代码(以获取 inactiveUsers)。如果您的 Linq 查询变得复杂,您会怎么做?您是否仍将 Linq 代码保留在控制器中或创建专用的 UserRepository 类?
  • @sharru 1. 如果您继续使用存储库的同一实例,它会这样做,但如果您使用两个不同的存储库则不会。 2.如果你真的不使用它们,那么这确实没有意义。 3. 您也可以为每个方法创建一次并将其包装在 using 语句中,但这不允许您使用延迟执行。您确实应该添加一个 Dispose 方法,您可以在控制器处理时调用该方法(在控制器中覆盖 Dispose)。我通常使用处理对象生命周期的 IoC 容器,但这对于小型项目来说可能太多了。
【解决方案2】:

我会重新考虑您是如何实现GetUsers() 的。您正在调用ToList(),这将导致基础表中的所有行都返回并存储在内存中。如果表变得足够大,您将遇到性能问题。最好返回 IQueryable&lt;User&gt; 并使用您的方法 return context.Users

当然你会遇到在你执行IQueryable&lt;&gt;时上下文已经被释放的问题,所以你需要以不同的方式处理上下文的生命周期。

如果项目足够小,那么你可以只在 Controller 级别存储一个 Context 的实例,并在释放控制器时释放它。如果您这样做,请确保您不会在视图中执行任何会导致执行其他查询的操作(例如,访问来自 User 的集合(如果存在)),否则会出错。

【讨论】:

    【解决方案3】:

    我的想法

    有什么缺点:

    • 您无法对使用您在 DAL 中定义的静态方法的任何地方进行真正的单元测试。
    • 它们还具有很强的耦合性,如果需要的话,它们在运行时更难更换。
    • 如果您需要在事务中提交多个更新,您可能会开始遇到更多复杂情况

    每次调用都创建一个新的 DbContext 有错吗?

    • 不,这很好。 DbContext 是轻量级的,旨在以这种方式使用。

    其他模式

    • 您已经提到了非常可靠的存储库模式,尤其是与工作单元模式一起使用时。

    你会使用 Linqtosql

    • 否 - Linqtosql 已基本完成,实体框架为这个问题提供了更完整且通常更好的解决方案

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-02
      • 1970-01-01
      • 2011-02-20
      • 2010-12-11
      • 1970-01-01
      相关资源
      最近更新 更多