【问题标题】:Non-ORM patterns / practices to abstract database interaction in C#?在 C# 中抽象数据库交互的非 ORM 模式/实践?
【发布时间】:2023-03-21 14:35:02
【问题描述】:

我是数据库/SQL/ADO.NET 新手,在尝试学习 NHibernate 或 Entity Framework 等 ORM 框架之前,我正在努力打下坚实的基础。我一直在阅读与平台无关的 RDBMS 概念,现在我正在尝试将其调整为编写与数据库实际交互的干净且可重用的 C# 代码。我听说过 Martin Fowler 的 Patterns of Enterprise Application Architecturehas been recommended here before,但标题中的“企业”让我想知道在我准备好阅读那本书之前是否需要进行一些中间学习步骤。

作为实践,我正在制作一个简单的 ASP.NET 网站,帮助访问者从包含数千条记录的数据库中查找某些信息。这是一个非常简单的第一个项目——没有任何类型的用户帐户或登录,该网站每天会获得大约十几个点击量。

我的 ASP.NET 代码提供了一些类似这样的方法来从数据库中检索数据:

string dbConnectString = "...";

public List<string> GetItems()
   {
   List<string> rList = null;

   using (SqlConnection myConn = new SqlConnection(dbConnectString))
      {
      string queryStatement = "SELECT Name FROM SomeTable";

      using (SqlCommand myCmd = new SqlCommand(queryStatement, myConn))
         {
         DataTable resultTable = new DataTable("Results");
         using (SqlDataAdapter myAdapter = new SqlDataAdapter(myCmd))
            {
            myConn.Open();
            if (myAdapter.Fill(resultTable) > 0)
               {
               // This loop should probably be a LINQ expression
               rList = new List<string>();
               foreach (DataRow row in resultTable.Rows)
                  {
                  rList.Add((string)row["Name"]);
                  }
               }

            myConn.Close();
            }
         }
      }

   return rList;
   }

我认为就处理实现 IDisposable 的对象而言,我做了正确的事情。但是有没有更好的方法来编写方法本身?

以下是我的一些问题:

  1. 根据我的阅读,似乎连接池makes it OK to instantiate a new SqlConnection 每次调用GetItems() 之类的方法。有没有一种“更干净”的方式来编写代码,所以我仍然会进行适当的资源管理,但不会像using (SqlConnection ...) 这样的块有太多重复?这种方法怎么样:

    public class DatabaseManager : IDisposable
       {
       protected SqlConnection myConn;
    
       public DatabaseManager(string connectionString)
          {
          // Set up the constructor
          myConn = new SqlConnection(dbConnectString);
          }
    
       // IDisposable implementation stuff goes here.  Any usage of 
       // DatabaseManager would have to take place in a using() block.
    
       public List<string> GetItems()
          {
          List<string> rList = null;
    
          string queryStatement = "SELECT Name FROM dbo.SomeTable";
    
          using (SqlCommand myCmd = new SqlCommand(queryStatement, myConn))
             {
             DataTable resultTable = new DataTable("Results");
             using (SqlDataAdapter myAdapter = new SqlDataAdapter(myCmd))
                {
                myConn.Open();
                if (myAdapter.Fill(resultTable) > 0)
                   {
                   rList = new List<string>();
                   foreach (DataRow row in resultTable.Rows)
                      {
                      rList.Add((string)row["Name"]);
                      }
                   }
    
                myConn.Close();
                }
             }
    
          return rList;
          }
       }
    
  2. 除了我无耻地使用垂直间距之外,还有更优雅的方法可以从数据库查询的结果中提取列表吗?一种方法是用 LINQ 调用替换 foreach() 迭代,但这只会节省两三行代码。

谢谢大家!

【问题讨论】:

    标签: c# asp.net


    【解决方案1】:

    您的代码既不干净也不可重用:

    1. sql 查询和列名被硬编码到管理器类中,这使得每个表都需要一个额外的管理器。您可以通过允许注入实际查询来解决此问题,但最终您会得到一个具有不同职责的“经理经理”
    2. sql 连接的生命周期与管理器的生命周期相关联,这使得无法通过复合数据库访问进行事务
    3. 没有错误处理
    4. 使用数据适配器首先检索数据,然后将其复制到列表中不如使用数据读取器有效

    要解决所有这些问题,您需要大量的时间和很多聪明的想法。您在使用单个选择查询的单个经理时遇到问题。几十个管理器怎么样?选择、更新、插入、删除并连接到其他管理器以及所有这些都具有适当的事务范围和错误处理?

    这就是为什么人们尽可能坚持使用 ORM,而不是重新发明轮子。编写一个好的、可重用的数据访问层并不容易,尽管您可能有一个适用于一两个简单案例的有效解决方案,但由于我提到的原因,这不会很好地扩展。迟早你会得到一大堆纯粹的混乱,在两三个不成功的方法之后,你最终会重新发明 ORM。

    【讨论】:

    • 感谢 Wiktor 的深刻见解。你提出了一个令人信服的案例。由于我是初学者,在学习 ORM 之前应该花多少时间来学习 SQL(或相关的必备概念)?
    • SQL 很重要,但我可能会有偏见,因为我知道。但是,在我看来,查询、过滤、排序、外部和内部连接或投影等基本概念很重要,更容易理解 ORM 试图实现的目标。 ado 的基本元素可能也很重要,但您似乎已经知道这些。 ORM 也有其陷阱,例如programmers.stackexchange.com/questions/100534/…
    猜你喜欢
    • 2013-09-22
    • 1970-01-01
    • 2012-05-12
    • 2010-10-08
    • 2020-08-17
    • 1970-01-01
    • 2013-10-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多