簡體   English   中英

存儲庫和數據映射器模式

[英]Repository and Data Mapper pattern

在大量關於Repository和Data Mapper的閱讀之后,我決定在測試項目中實現這些模式。 由於我是這些人的新手,我想了解一下如何在一個簡單的項目中實現這些。

傑里米·米勒說:

做一些非常重要的個人編碼項目,你可以自由地嘗試設計模式。

但我不知道我做了所有這些事情是對的。

這是我的項目結構:

在此輸入圖像描述

正如您所看到的,我將在下面詳細介紹它們。

  • 域:項目域實體到這里我有一個簡單的Personnel類,它繼承自EntityBase類,EntityBase類有一個名為Id的屬性。

     public int Id { get; set; } 
  • Infrustructure:這是一個簡單的數據訪問層,有兩個類。 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);
}

Repository.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的引用。 每個Entities Repository類發送的從Repository類繼承的參數。 例如 :

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

    }
}

正如你在FindOne方法中看到的那樣,我試圖自動化一些操作,比如創建CommandText,然后我利用我的DataLayer類來配置命令,最后執行命令來獲取IDataReader。 我將IDataReader傳遞給我的DataMapper類以映射到實體。

  • DomainMapper:最后在這里我將IDataReader的結果映射到實體,下面是我如何映射Personnel實體的示例:

     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"]) }; }} 

用法:

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

我知道我在這里犯了很多錯誤,這就是我在這里的原因。 我需要你的建議才能知道我做錯了什么或者在這個簡單的測試項目中有什么好處。

提前致謝。

幾點:

  1. 總體來說,你有一個很好的設計讓我感到震驚。 部分原因在於,您可以對其進行更改,而對變更之外的任何類別(低耦合)幾乎沒有影響。 也就是說,它與實體框架的作用非常接近,因此雖然它是一個很好的個人項目,但我會考慮在生產項目中實施EF之前先使用EF。

  2. 您可以使用反射使您的DataMapper類成為通用的(例如, GenericDataMapper<T> )。 使用反射迭代類型T的屬性 ,並動態地從數據行獲取它們。

  3. 假設您確實創建了一個Generic DataMapper,您可以考慮在DataLayer上創建一個CreateRepository<T>()方法,這樣用戶就不必擔心要選擇哪種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. 最后但並非最不重要的是,與傑里米合作我肯定他會說你應該為所有這些課程進行單元測試!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM