【问题标题】:ASP.NET MVC guidelines for static classes for database access用于数据库访问的静态类的 ASP.NET MVC 指南
【发布时间】:2016-05-23 20:52:36
【问题描述】:

我目前在我的 ASP.NET 应用程序(使用实体框架)中使用 MVC 模式的方式如下:

1) 我的 Models 文件夹包含所有 EF 实体,以及我的 ViewModels

2) 我有一个 Helpers 文件夹,用于存储为特定应用程序创建的类。

3) 在我的 Helpers 文件夹中,我有一个名为 MyHelper 的静态类,其中包含使用 EF 访问数据库的方法。

namespace myApp.Helpers
{
    public static class MyHelper
    {
        public static async Task<ProductVM> GetProductAsync(int productId)
        {
            using (var context = new myEntities())
            {
                return await context.vwxProducts.Where(x => x.ProductId == productId).Select(x => new ProductVM { A = x.A, B = x.B }).FirstOrDefaultAsync();
            }
        }
    }
}

4) 我的控制器然后在必要时调用这些函数:

namespace myApp.Controllers
{
    public class ProductController : Controller
    {

        [HttpGet]
        public async Task<ActionResult> Index(int productId)
        {
            var productVM = await MyHelper.GetProductAsync(productId);
            return View(productVM);
        }
    }
}

我通常在 SO 中遇到“不要使用静态类,静态类是邪恶的,等等”类型的 cmets。这适用于这种情况吗?如果是,为什么?我的应用是否应该遵循更好的“结构”以实现最佳实践并避免此类陷阱?

【问题讨论】:

  • 静态类没有任何问题,只要它有意义。这个类会需要一个实例吗?它是否需要扩展一个类或被另一个类扩展?但总的来说,随着需求的变化,给自己戴上手铐是没有意义的。
  • 你见过this SO question

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


【解决方案1】:

您不能真正为此使用静态类。您的实体框架上下文每个请求应该有一个且只有一个实例。您在此处的方法为每个方法实例化一个新上下文,这将导致 Entity Framework 出现大量问题。

一般概念很好,但是您的MyHelper 类应该是普通类。添加一个获取上下文实例的构造函数,然后使用 DI 容器将上下文注入到辅助类中,并将辅助类注入到您的控制器中。

更新

帮手

namespace myApp.Helpers
{
    public class MyHelper
    {
        private readonly DbContext context;

        public MyHelper(DbContext context)
        {
            this.context = context;
        }

        public async Task<ProductVM> GetProductAsync(int productId)
        {
            return await context.vwxProducts.Where(x => x.ProductId == productId).Select(x => new ProductVM { A = x.A, B = x.B }).FirstOrDefaultAsync();
        }
    }
}

控制器

namespace myApp.Controllers
{
    public class ProductController : Controller
    {
        private readonly MyHelper myHelper;

        public ProductController(MyHelper myHelper)
        {
            this.myHelper = myHelper;
        }

        [HttpGet]
        public async Task<ActionResult> Index(int productId)
        {
            var productVM = await myHelper.GetProductAsync(productId);
            return View(productVM);
        }
    }
}

然后,您只需要设置一个 DI 容器来注入所有内容。代码完全取决于你最终使用的容器,所以我真的无法进一步帮助你。不过,这通常很简单。只需阅读容器的文档。您需要将对象的生命周期范围设置为请求。同样,不同的容器是不同的,但它们都有某种请求范围。

【讨论】:

  • 谢谢,你能举个例子吗?这有时让我感到困惑,我记得在 MSDN 博客上看到过我的方法所依据的示例。
  • 另外,FWIW,你不需要 async 和 await 对你的个人帮助方法,除非他们做的不仅仅是等待一个调用。只需返回 Task&lt;ProductVM&gt; 并在您的控制器中等待。这是一个小的优化,但它消除了一层开销。
  • 谢谢@Chris,看来我有一些重构要做:)
  • 嗨@Chris,为迟到的评论道歉,但我需要澄清你的代码sn-p:我已经开始使用Autofac.MVC5 实现你的方法。在上面的代码 sn-p 中,您注入了 DbContext,而在我原来的问题中,我使用了 myEntities 类(继承 DbContext,由我的数据库中的 EF 自动生成)。这意味着在助手类中,context 需要改为myEntities,这样我就可以使用builder.RegisterType&lt;myEntities&gt;().AsSelf().InstancePerRequest(); 向 Autofac 注册它,对吗?
  • 您需要通用访问 DbSet,即context.Set&lt;MyEntity&gt;().Where(...);
【解决方案2】:

我想对 ChrisPratt 的答案添加评论,但结果太长,所以让我添加单独的答案。

基本上,这不是生死攸关的选择。当然,静态方法不如用于数据库访问的类灵活。但它们本身并不坏。每个请求一个 DbContext 是目标。这不是绝对必须的。它是kinda like dependency injection - 您可以获得更大的灵活性,进而增加代码复杂性。

看看这三个问题及其答案,考虑到他们所说的一切,我相信您将能够自己回答您的问题:

编辑:克里斯对我的回答留下了很好的评论,我已经稍微改变了回答以考虑到他所说的话。

【讨论】:

  • 为了安全起见,每个请求一个上下文过于简单化。对于每个离散的操作集,您确实只需要一个上下文,但由于通常一个请求涉及一组离散的操作,因此将生命周期设置为请求范围会更容易。然而,说这仅仅是一个“建议”就太过分了。创建一个方法范围的上下文,就像 OP 所做的那样在各种情况下导致异常。如果您尝试关联以这种方式检索到的两个对象,或者甚至尝试延迟加载关系,BOOM - 您的应用会爆炸。
  • 好吧,这么说吧——你应该针对每组动作的上下文而不是混淆它们——我同意。让我担心的是一个 DbContext 用于不同的操作集的场景 - 代码中您最终使用多个 db.SaveChanges() 或一个 SaveChanges 会产生意外结果,因为 DbContext 与其他一些对象纠缠在一起。无论如何,我会编辑我的答案以指向您的评论,感谢您留下它。
  • 那里也没有问题。使用相同的上下文执行多个事务没有缺点,虽然为不同的事务新建上下文在技术上当然没有错,但除了将另一个对象添加到堆和创建它的 CPU 时间之外,它没有任何作用。也许,最好声明一下,虽然您并不严格要求每个请求只需要一个上下文,但以任何其他方式执行此操作几乎没有意义。
  • 有趣的讨论,谢谢大家。所有这一切的不幸之处在于,在线 MVC 文档/教程通常有完全不同的实现,没有任何理由——对于像我这样的新手来说,东西很快就会变得混乱。如果你们有任何好的 MVC 资源/书籍建议我全神贯注。
  • 是的,像 asp.net/get-started 这样的微软资源网站是由不同的作者完成的,当你有不同的人作为承包商支付报酬时,你最终会得到不同的结果。所以,没有银弹。只需阅读所有内容并自行决定。一旦您找到“与您交谈”的作者,请查看他是否写了一本书并继续阅读。我个人最喜欢的 MS 作者是 Mike Taulty - mtaulty.com 但我不知道他是否在 ASP.NET 上写了这么多
【解决方案3】:

你的想法是正确的,我一直在使用它。但风格是这样的: 1)对于每个实体(即用户),我们在 Providers 文件夹中有一个静态类。在这个类中,我们可以做通用方法(即 create、Get、GetAll 、..)

    public static class Users
{
    public static IEnumerable<kernel_Users> GetAll()
    {
        Kernel_Context db = new Kernel_Context();
        return db.kernel_Users;
    }

public static kernel_Users Get(int userId)
    {
        Kernel_Context db = new Kernel_Context();
        return db.kernel_Users.Where(c => c.UserId == userId).FirstOrDefault();
    }
    ...
}

2) 我们还有另一个非静态类。它位于 Models 文件夹中。这是我们可以访问实体实例的地方:

    public partial class kernel_Users
{
    [Key]
    public int UserId { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }

    [NotMapped]
    public string FullName
    {
        get
        {
            return FirstName + " " + LastName;
        }
    }
    public bool Delete(out string msg)
    {
        ...
    }
    ...

}

【讨论】:

    【解决方案4】:

    我使用一个静态类,该类将上下文注入到静态构造函数中,以加载很少更改的数据缓存。它(应该)是线程安全的。我希望这对您有所帮助,根据我的经验,它非常方便:

     public static class StaticCache<T>  where T: class 
     {
        private static List<T> dbSet;
        public static Dictionary<string, List<T>> cache = new Dictionary<string, List<T>>();
        private static readonly object Lock = new object();
        public static void Load(DbContext db, string connStr, string tableName)
        {
            lock (Lock)
            {
                try
                {
                    if (connStr != null)
                    {
                        using (db)
                        {
                            dbSet = db.Set<T>().ToList();                            
                            cache.Add(tableName, dbSet);
                        }
                    }
    
                }
                catch { }
            }
        }
     }
     void Testit() 
     {
        var context = new YourContextSubClass(connStr);       
        StaticCache<TableEntity>.Load(context, connstr, "tableEntityNameString");
     }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-12
      • 1970-01-01
      • 1970-01-01
      • 2016-07-19
      • 2018-03-17
      • 2014-06-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多