存储库和数据映射器模式

2024-04-29

在大量阅读有关存储库和数据映射器的内容后,我决定在测试项目中实现这些模式。由于我对这些不熟悉,我想了解您对我如何在一个简单的项目中实现这些的看法。

杰里米·米勒 说:

做一些不平凡的个人编码项目,您可以在其中自由地尝试设计模式。

但我不知道我做的这一切是否正确。

这是我的项目结构:

正如您所看到的,有很多文件夹,我将在下面详细描述它们。

  • 域:项目域实体放在这里我有一个简单的 Personnel 类,它继承自 EntityBase 类,EntityBase 类有一个名为 Id 的属性。

    public int Id { get; set; }
    
  • 基础设施:这是一个包含两个类的简单数据访问层。 SqlDataLayer 是一个简单的类,它继承自名为DataLayer 的抽象类。在这里我提供了一些功能,例如以下代码:

    public SQLDataLayer() {
        const string connString = "ConnectionString goes here";
        _connection = new SqlConnection(connString);
        _command = _connection.CreateCommand();
    }
    

将参数添加到命令参数集合:

    public override void AddParameter(string key, string value) {
        var parameter = _command.CreateParameter();
        parameter.Value = value;
        parameter.ParameterName = key;

        _command.Parameters.Add(parameter);
    }

执行 DataReader :

    public override IDataReader ExecuteReader() {
        if (_connection.State == ConnectionState.Closed)
            _connection.Open();

        return _command.ExecuteReader();
    }

等等。

  • 存储库:这里我尝试实现存储库模式。 IRepository 是一个通用接口

IRepository.cs:

public interface IRepository<TEntity> where TEntity : EntityBase
{
    DataLayer Context { get; }

    TEntity FindOne(int id);
    ICollection<TEntity> FindAll();

    void Delete(TEntity entity);
    void Insert(TEntity entity);
    void Update(TEntity entity);
}

存储库.cs:

public class Repository<TEntity> : IRepository<TEntity> where TEntity : EntityBase, new() {
    private readonly DataLayer _domainContext;
    private readonly DataMapper<TEntity> _dataMapper;
    public Repository(DataLayer domainContext, DataMapper<TEntity> dataMapper) {
        _domainContext = domainContext;
        _dataMapper = dataMapper;
    }
    public DataLayer Context {
        get { return _domainContext; }
    }
    public TEntity FindOne(int id)
    {
        var commandText = AutoCommand.CommandTextBuilder<TEntity>(CommandType.StoredProcedure, MethodType.FindOne);

        // Initialize parameter and their types
        Context.AddParameter("Id", id.ToString(CultureInfo.InvariantCulture));
        Context.SetCommandType(CommandType.StoredProcedure);
        Context.SetCommandText(commandText);

        var dbReader = Context.ExecuteReader();
        return dbReader.Read() ? _dataMapper.Map(dbReader) : null;
    }

我没有公开 IRepository 中未实现的方法。

在 Generic Repository 类中,我期望构造函数中有两个参数,第一个是对 SqlDataLayer 类的引用,第二个是对 Entity DataMapper 的引用。 这些参数由继承自 Repository 类的每个 Entities Repository 类发送。例如 :

public class PersonnelRepository : Repository<Personnel>, IPersonnelRepository {
    public PersonnelRepository(DataLayer domainContext, PersonnelDataMapper dataMapper)
        : base(domainContext, dataMapper) {

    }
}

正如您在 FindOne 方法中看到的,我尝试自动化一些操作,例如创建 CommandText,然后我利用 DataLayer 类来配置命令,最后执行命令来获取 IDataReader。我将 IDataReader 传递给 DataMapper 类以映射到实体。

  • DomainMapper:最后,我将 IDataReader 的结果映射到实体,下面是如何映射人员实体的示例:

    public class PersonnelDataMapper : DataMapper<Personnel> {
    public override Personnel Map(IDataRecord record) {
        return new Personnel {
            FirstName = record["FirstName"].ToString(),
            LastName = record["LastName"].ToString(),
            Address = record["Address"].ToString(),
            Id = Convert.ToInt32(record["Id"])
        };
    }}
    

Usage :

    using (var context = new SQLDataLayer()) {
        _personnelRepository = new PersonnelRepository(context, new PersonnelDataMapper());
            var personnel  = _personnelRepository.FindOne(1);
    }

我知道我在这里犯了很多错误,这就是我来这里的原因。我需要您的建议来了解我做错了什么或者这个简单的测试项目有哪些优点。

提前致谢。


几点:

  1. 总体而言,我觉得你们的设计很好。这在一定程度上证明了这一点,即您可以对其进行更改,而对已更改的类之外的任何类影响很小(低耦合)。也就是说,它与实体框架的功能非常接近,因此虽然这是一个很好的个人项目,但我会考虑先使用 EF,然后再在生产项目中实现它。

  2. 您的 DataMapper 类可以变得通用(例如,GenericDataMapper<T>)使用反射。使用反射迭代 T 类型的属性 https://stackoverflow.com/questions/531384/how-to-loop-through-all-the-properties-of-a-class,并动态地从数据行中获取它们。

  3. 假设您确实制作了一个 Generic DataMapper,您可以考虑制作一个CreateRepository<T>()DataLayer 上的方法,这样用户就不需要担心选择哪种类型的 Mapper 的细节。

  4. 一个小批评——您假设所有实体都有一个名为“Id”的整数 ID,并且将设置一个存储过程来通过这样的方式检索它们。您可以通过允许不同类型的主键(同样可以使用泛型)来改进您的设计。

  5. 您可能不想像您那样重复使用 Connection 和 Command 对象。这不是线程安全的,即使是,您最终也会在数据库事务周围遇到一些令人惊讶且难以调试的竞争条件。您应该为每个函数调用创建新的 Connection 和 Command 对象(确保在完成后处理它们),或者围绕访问数据库的方法实现一些同步。

例如,我建议使用 ExecuteReader 的替代版本:

public override IDataReader ExecuteReader(Command command) {
    var connection = new SqlConnection(connString);
    command.Connection = connection;
    return command.ExecuteReader();
}

您的旧命令重新使用了命令对象,这可能会导致多线程调用者之间出现竞争条件。您还想创建一个新连接,因为旧连接可能正在参与由不同调用者启动的事务。如果要重用事务,则应创建连接、开始事务,然后重用该事务,直到执行完要与该事务关联的所有命令为止。例如,您可以创建 ExecuteXXX 方法的重载,如下所示:

public override IDataReader ExecuteReader(Command command, ref SqlTransaction transaction) {
    SqlConnection connection = null;
    if (transaction == null) {
        connection = new SqlConnection(connString);
        transaction = connection.BeginTransaction();
    } else {
        connection = transaction.Connection;
    }
    command.Connection = connection;
    return command.ExecuteReader();
}    

// When you call this, you can pass along a transaction by reference.  If it is null, a new one will be created for you, and returned via the ref parameter for re-use in your next call:

SqlTransaction transaction = null;

// This line sets up the transaction and executes the first command
var myFirstReader = mySqlDataLayer.ExecuteReader(someCommandObject, ref transaction);

// This next line gets executed on the same transaction as the previous one.
var myOtherReader = mySqlDataLayer.ExecuteReader(someOtherCommandObject, ref transaction);

// Be sure to commit the transaction afterward!
transaction.Commit();

// Be a good kid and clean up after yourself
transaction.Connection.Dispose();
transaction.Dispose();
  1. 最后但并非最不重要的一点是,与杰里米一起工作后,我确信他会说你应该对所有这些类进行单元测试!
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

存储库和数据映射器模式 的相关文章

  • EF Core Group By 翻译支持条件总和

    听说 EF Core 2 1 将支持翻译小组 我感到非常兴奋 我下载了预览版并开始测试它 但发现我在很多地方仍然没有得到翻译分组 在下面的代码片段中 对 TotalFlagCases 的查询将阻止翻译分组工作 无论如何 我可以重写这个以便我
  • 为什么两个不同的 Base64 字符串的转换会返回相等的字节数组?

    我想知道为什么从 base64 字符串转换会为不同的字符串返回相同的字节数组 const string s1 dg const string s2 dq byte a1 Convert FromBase64String s1 byte a2
  • 在结构中使用 typedef 枚举并避免类型混合警告

    我正在使用 C99 我的编译器是 IAR Embedded workbench 但我认为这个问题对于其他一些编译器也有效 我有一个 typedef 枚举 其中包含一些项目 并且我向该新类型的结构添加了一个元素 typedef enum fo
  • 查找c中结构元素的偏移量

    struct a struct b int i float j x struct c int k float l y z 谁能解释一下如何找到偏移量int k这样我们就可以找到地址int i Use offsetof 找到从开始处的偏移量z
  • Asp.NET WebApi 中类似文件名称的路由

    是否可以在 ASP NET Web API 路由配置中添加一条路由 以允许处理看起来有点像文件名的 URL 我尝试添加以下条目WebApiConfig Register 但这不起作用 使用 URIapi foo 0de7ebfa 3a55
  • 类模板参数推导 - clang 和 gcc 不同

    下面的代码使用 gcc 编译 但不使用 clang 编译 https godbolt org z ttqGuL template
  • 从Web API同步调用外部api

    我需要从我的 Web API 2 控制器调用外部 api 类似于此处的要求 使用 HttpClient 从 Web API 操作调用外部 HTTP 服务 https stackoverflow com questions 13222998
  • 用于登录 .NET 的堆栈跟踪

    我编写了一个 logger exceptionfactory 模块 它使用 System Diagnostics StackTrace 从调用方法及其声明类型中获取属性 但我注意到 如果我在 Visual Studio 之外以发布模式运行代
  • OleDbDataAdapter 未填充所有行

    嘿 我正在使用 DataAdapter 读取 Excel 文件并用该数据填充数据表 这是我的查询和连接字符串 private string Query SELECT FROM Sheet1 private string ConnectStr
  • 在 ASP.NET 5 中使用 DI 调用构造函数时解决依赖关系

    Web 上似乎充斥着如何在 ASP NET 5 中使用 DI 的示例 但没有一个示例显示如何调用构造函数并解决依赖关系 以下只是众多案例之一 http social technet microsoft com wiki contents a
  • 使用 WebClient 时出现 System.Net.WebException:无法创建 SSL/TLS 安全通道

    当我执行以下代码时 System Net ServicePointManager ServerCertificateValidationCallback sender certificate chain errors gt return t
  • C#中如何移动PictureBox?

    我已经使用此代码来移动图片框pictureBox MouseMove event pictureBox Location new System Drawing Point e Location 但是当我尝试执行时 图片框闪烁并且无法识别确切
  • 重载<<的返回值

    include
  • 如何序列化/反序列化自定义数据集

    我有一个 winforms 应用程序 它使用强类型的自定义数据集来保存数据进行处理 它由数据库中的数据填充 我有一个用户控件 它接受任何自定义数据集并在数据网格中显示内容 这用于测试和调试 为了使控件可重用 我将自定义数据集视为普通的 Sy
  • 垃圾收集器是否在单独的进程中运行?

    垃圾收集器是否在单独的进程中启动 例如 如果我们尝试测量某段代码所花费的进程时间 并且在此期间垃圾收集器开始收集 它会在新进程上启动还是在同一进程中启动 它的工作原理如下吗 Code Process 1 gt Garbage Collect
  • 如何查看网络连接状态是否发生变化?

    我正在编写一个应用程序 用于检查计算机是否连接到某个特定网络 并为我们的用户带来一些魔力 该应用程序将在后台运行并执行检查是否用户请求 托盘中的菜单 我还希望应用程序能够自动检查用户是否从有线更改为无线 或者断开连接并连接到新网络 并执行魔
  • 覆盖子类中的字段或属性

    我有一个抽象基类 我想声明一个字段或属性 该字段或属性在从该父类继承的每个类中具有不同的值 我想在基类中定义它 以便我可以在基类方法中引用它 例如覆盖 ToString 来表示 此对象的类型为 property field 我有三种方法可以
  • 测试用例执行完成后,无论是否通过,如何将测试用例结果保存在变量中?

    我正在使用 NUNIT 在 Visual Studio 中使用 Selenium WebDriver 测试用例的代码是 我想在执行测试用例后立即在变量中记录测试用例通过或失败的情况 我怎样才能实现这一点 NUnit 假设您使用 NUnit
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new
  • 对来自流读取器的过滤数据执行小计

    编辑问题未得到解答 我有一个基于 1 个标准的过滤输出 前 3 个数字是 110 210 或 310 给出 3 个不同的组 从流阅读器控制台 问题已编辑 因为第一个答案是我给出的具体示例的字面解决方案 我使用的实际字符串长度为 450 个

随机推荐