在 C# 中访问数据库的最佳方法(设计模式)是什么?

Whats the best approach (design pattern) to access database in C#?

我是设计模式的新手。 目前我正在开发一个有关系数据库的系统。从我的数据库中进行 CRUD 的最佳方法是什么? 我当前的代码如下所示(C# 代码):

我为所有 classes 定义了一个具有公共功能的接口。

namespace Model
{
    public interface ICommon
    {
        void insert();
        void update();
        void delete();
    }
}

Common class(抽象一)实现了 ICommon 接口和一些命令方法和属性。

namespace Model
{
    public abstract class Common : ICommon
    {
        public Guid RecId { set; get; }

        public abstract void insert();
        public abstract void update();
        public abstract void delete();
        public abstract List<Common> find();

        /// <summary>
        /// Insert or update the record
        /// </summary>
        public void save()
        {
            if (this.RecId == Guid.Empty)
            {
                this.insert();
            }
            else
            {
                this.update();
            }
        }
    }
}

然后,适当的 class(例如 UserTable class)扩展 Common class 并实现抽象方法和其他细节属性。

我通过 StoresProcedures 和 SqlParameter、SqlCommand 和 SqlConnection 进行 CRUD 的方式。这是一个例子:

    class CustTableModel : Common
        {
            public string SerialNumber { set; get; }
            public string ApplicationVersion { set; get; }
            public string KernelVersion { set; get; }
            public string Name { set; get; }
            public bool Active { set; get; }

            public override void insert()
            {
                List<SqlParameter> parameters = new List<SqlParameter>();
                SqlParameter parameter;

                // SerialNumber
                parameter = new SqlParameter("@serialNumber", System.Data.SqlDbType.Int);
                parameter.Value = this.SerialNumber;
                parameters.Add(parameter);

                // ApplicationVersion
                parameter = new SqlParameter("@applicationVersion", System.Data.SqlDbType.Int);
                parameter.Value = this.ApplicationVersion;
                parameters.Add(parameter);

                // KernelVersion
                parameter = new SqlParameter("@kernelVersion", System.Data.SqlDbType.Int);
                parameter.Value = this.KernelVersion;
                parameters.Add(parameter);

                // Name
                parameter = new SqlParameter("@name", System.Data.SqlDbType.Int);
                parameter.Value = this.Name;
                parameters.Add(parameter);

                // Active
                parameter = new SqlParameter("@active", System.Data.SqlDbType.Bit);
                parameter.Value = this.Active;
                parameters.Add(parameter);

                DBConn.execute("CUSTTABLE_INSERT", parameters); // The code of DBConn is below.
}
}

为了更好的理解,这里是 DBConn class:

public class DBConn
    {
        protected SqlConnection sqlConnection;
        protected string command { set; get; }
        protected List<SqlParameter> parameters { set; get; }

        protected void openConnection()
        {
            this.sqlConnection = new SqlConnection();
            this.sqlConnection.ConnectionString = "Data Source=.\SQLEXPRESS;Initial Catalog=JYL_SOAWS_DB;Integrated Security=True";
            this.sqlConnection.Open();
        }

        protected void closeConnection()
        {
            if (this.sqlConnection.State == System.Data.ConnectionState.Open)
            {
                this.sqlConnection.Close();
            }
        }

        /// <summary>
        /// Executa o processo no banco.
        /// </summary>
        /// <returns>Quantidade de registros afetados.</returns>
        protected SqlDataReader run()
        {
            SqlCommand command = new SqlCommand();
            SqlDataReader ret;

            this.openConnection();

            command.CommandType = System.Data.CommandType.StoredProcedure;
            command.Connection = this.sqlConnection;
            command.CommandText = this.command;

            if (this.parameters != null)
            {
                foreach (SqlParameter parameter in this.parameters)
                {
                    command.Parameters.Add(parameter);
                }
            }

            ret = command.ExecuteReader();

            this.closeConnection();

            return ret;
        }

        /// <summary>
        /// Interface da classe à outros objetos.
        /// </summary>
        /// <param name="commandName">Nome da store procedure a ser executada.</param>
        /// <param name="parameters">A lista com os parâmetros e valores.</param>
        /// <returns>Numero de registros afetados.</returns>
        public static SqlDataReader execute(string commandName, List<SqlParameter> parameters = null)
        {
            DBConn conn = new DBConn();

            conn.command = commandName;
            conn.parameters = parameters;

            return conn.run();
        }
    }

我很确定有更好的方法。

有人能帮帮我吗?谢谢提前。

您在这里发现了两种截然不同的模式。

第一个是 repository pattern - 一种从数据访问中抽象出业务逻辑的方法

第二种是 Active Record 模式,实体负责在数据库中维护自己的状态。

我建议您远离 C# 中的 ActiveRecord(您现在可能知道也可能不知道 Inversion of Control 模式,但它非常有用并且与 AR 相当不兼容)。

如果您刚开始,我建议您看看 dapper.net 之类的东西(我仍然在我的小项目中使用它)。它是一个 Micro-ORM,它从使用数据库中拿走了很多样板,没有固执己见或难以学习(我使用并喜欢 EntityFramework 和 NHibernate,但它们在任何地方都不那么容易获取初学者)。

除此之外,我将创建一个存储库(class 具有 Create(Foo entity)、Read(Guid entityId)、Update(Foo entity) 和 Delete(Guid entityId) 方法)。

顺便说一句,使用 Guid 作为主键时要小心,因为它们会导致一个有趣的情况:因为大多数 Guid 实现(几乎总是)具有非顺序布局,并且数据按主键物理排序,这样的插入会导致大量磁盘 IO,因为数据库会重新排序磁盘上的数据页以容纳插入到 table 中任意位置的新数据。 Guid 生成用作主键的一个好策略是使用 Guid Comb 生成器

祝你好运!

这是最好的模式。我建议 不要 使用 ORM。特别是 EF.

public class MyModel 
{
    public string Id {get;set;}
    //public valuetype PropertyA {get;set;}  //other properties
}

public interface IMyModelRepository
{
    List<MyModel> GetModels();

    MyModel GetModelById(string id);

    void AddMyModel(MyModel model);
    //other ways you want to get models etc
}

public class MyModelRepositorySql : IMyModelRepository
{
    public List<MyModel> GetModels()
    {
        //SqlConnection etc etc
        while (SqlDataReader.Read())
        {
           results.Add(this.populateModel(dr));
        }
        return results;
    }

    protected MyModel populateModel(SqlDataReader dr)
    {
        //map fields to datareader
    }

    public MyModel GetModelById(string id)
    {
        //sql conn etc
        this.populateModel(dr);
    }
}

这是我的推理:

使用存储库模式允许您注入不需要数据库的持久化数据的方法。这对于单元测试是必不可少的,但如果您可以将模拟存储库注入项目以进行集成测试,您也会发现它非常有用。

虽然 ORM 起初看起来很简单并且可以节省您大量的输入,但它们在较长的 运行 中会导致问题。您只需要在堆栈溢出中搜索 entity framework 个问题,就可以了解人们在以次优方式遇到 运行 个查询时遇到的问题。

在任何大型项目中,您都会 运行 遇到数据获取需求,这需要一些优化的数据检索方式,这会破坏您精心设计的对象 graph/injectable 通用存储库或巧妙的尖端 ORM .

POCO 对象很好。当您尝试将复杂对象(具有其他对象作为属性的对象)序列化或递归添加到数据库等时,复杂对象(具有其他对象作为属性的对象)是一件很痛苦的事情。保持底层数据模型 POCO 并且仅使用 LINQ 将它们组合在服务或视图模型中。

使用 GUID 干得好 ids 顺便说一句!别听信那些认为自己永远 运行 出不了 ints 的傻瓜! (存储为 varchar(50) 并让 DBA 整理索引)任何数据库生成的 ID 的问题是您必须能够在不连接到数据库的情况下创建对象。

为了执行 CRUD 操作,我建议使用 Entity framework 的存储库模式。

Entity Framework是微软提供的ORM。它使用一组 POCO 类(实体)来处理数据库以执行 insert/update/delete/create 操作。

要对这些实体执行查询,将使用语言集成查询 (LINQ)。 LINQ 使用与 SQL 类似的语法,它 returns 数据库结果作为实体集合。

这是一个示例 Repository pattern with EF

干杯!