3

私はここ 1 年ほど C# を学んでおり、その過程でベスト プラクティスを取り入れようとしています。StackOverflow と他の Web リソースの間で、懸念事項を適切に分離するための正しい軌道に乗っていると思っていましたが、今はいくつかの疑問があり、Web サイト全体をこの新しいものに変換する前に、正しい道を進んでいることを確認したいと考えています。建築。

現在の Web サイトは古い ASP VBscript であり、既存のデータベースは非常に醜い (外部キーなどがない) ため、少なくとも .NET の最初のバージョンでは使用したくなく、現時点で ORM ツールを学習する必要はありません。

UI レイヤーは DTO とビジネス レイヤーのみを表示でき、データ レイヤーはビジネス レイヤーからのみ表示できるように、個別の名前空間とセットアップにある次の項目があります。簡単な例を次に示します。

productDTO.cs

public class ProductDTO
{
    public int ProductId { get; set; }
    public string Name { get; set; }

    public ProductDTO()
    {
        ProductId = 0;
        Name = String.Empty;
    }
}

productBLL.cs

public class ProductBLL
{

    public ProductDTO GetProductByProductId(int productId)
    {
        //validate the input            
        return ProductDAL.GetProductByProductId(productId);
    }

    public List<ProductDTO> GetAllProducts()
    {
        return ProductDAL.GetAllProducts();
    }

    public void Save(ProductDTO dto)
    {
        ProductDAL.Save(dto);
    }

    public bool IsValidProductId(int productId)
    {
        //domain validation stuff here
    }
}

productDAL.cs

public class ProductDAL
{
    //have some basic methods here to convert sqldatareaders to dtos


    public static ProductDTO GetProductByProductId(int productId)
    {
        ProductDTO dto = new ProductDTO();
        //db logic here using common functions 
        return dto;
    }

    public static List<ProductDTO> GetAllProducts()
    {
        List<ProductDTO> dtoList = new List<ProductDTO>();
        //db logic here using common functions 
        return dtoList;
    }

    public static void Save(ProductDTO dto)
    {
        //save stuff here
    }

}

私の UI では、次のようにします。

ProductBLL productBll = new ProductBLL();
List<ProductDTO> productList = productBll.GetAllProducts();

保存の場合:

ProductDTO dto = new ProductDTO();
dto.ProductId = 5;
dto.Name = "New product name";
productBll.Save(dto);

私は完全に基地から外れていますか?BLL にも同じプロパティを設定し、DTO を UI に戻さないようにする必要がありますか? 何が間違っていて何が正しいのか教えてください。私はまだ専門家ではないことに注意してください。

アーキテクチャにインターフェイスを実装したいのですが、その方法をまだ学んでいます。

4

4 に答える 4

2

追加を検討したいもの: 検証、プロパティ変更通知、データ バインディングなど... 各クラスを複数のクラス (DAL、BLL など) に分離する場合の一般的な問題の 1 つは、多くの場合、多くのコードが作成されることです。複製する必要があります。もう 1 つの問題は、これらのクラス間に親密さが必要な場合、内部メンバー (インターフェース、フィールドなど) を作成する必要があることです。

これは私が行うことであり、次のような独自の一貫したドメイン モデルを構築します。

public class Product: IRecord, IDataErrorInfo, INotifyPropertyChanged
{
    // events
    public event PropertyChangedEventHandler PropertyChanged;

    // properties
    private int _id;
    public virtual int Id
    {
        get
        {
            return _id;
        }
        set
        {
            if (value != _id)
            {
                _id = value;
                OnPropertyChanged("Id");
            }
        }
    }

    private string _name;
    public virtual string Name
    {
        get
        {
            return _name;
        }
        set
        {
            if (value != _name)
            {
                _name = value;
                OnPropertyChanged("Name");
            }
        }
    }

    // parameterless constructor (always useful for serialization, winforms databinding, etc.)
    public Product()
    {
        ProductId = 0;
        Name = String.Empty;
    }

    // update methods
    public virtual void Save()
    {
       ValidateThrow();
       ... do save (insert or update) ...
    }

    public virtual void Delete()
    {
       ... do delete ...
    }    

    // validation methods
    public string Validate()
    {
       return Validate(null);
    }

    private void ValidateThrow()
    {
      List<Exception> exceptions = new List<Exception>();
      SummaryValidate(exceptions,memberName);
      if (exceptions.Count != 0)
         throw new CompositeException(exceptions);
    }

    public string Validate(string memberName)
    {
      List<Exception> exceptions = new List<Exception>();
      SummaryValidate(exceptions,memberName);
      if (exceptions.Count == 0)
        return null;

      return ConcatenateAsString...(exceptions);
    }

    string IDataErrorInfo.Error
    {
      get
      {
         return Validate();
      }
    }

    string IDataErrorInfo.this[string columnName]
    {
      get
      {
        return validate(columnName);
      }
    }

    public virtual void SummaryValidate(IList<Exception> exceptions, string memberName)
    {
       if ((memberName == null) || (memberName == "Name"))
       {
         if (!... validate name ...)
            exceptions.Add(new ValidationException("Name is invalid");
       }
    }

    protected void OnPropertyChanged(string name)
    {
       OnPropertyChanged(new PropertyChangedEventArgs(name));
    }

    // property change notification
    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        if ((PropertyChanged != null)
            PropertyChanged(this, e);
    }

    // read from database methods
    protected virtual Read(IDataReader reader)
    {
      Id = reader.GetInt32(reader.GetOrdinal("Id"));
      Name = = reader.GetString(reader.GetOrdinal("Id"));
      ...
    }

    void IRecord.Read(IDataReader reader)
    {
      Read(reader);
    }

    // instance creation methods
    public static Product GetById(int id)
    {
        // possibly use some cache (optional)
        Product product = new Product();
        using (IDataReader reader = GetSomeReaderForGetById...(id))
        {
            if (!reader.Read())
              return null;

            ((IRecord)product).Read(reader);
            return product;
        }
    }

    public static List<Product> GetAll()
    {
        // possibly use some cache (optional)
        List<Product> products = new List<Product>(); // if you use WPF, an ObservableCollection would be more appropriate?
        using (IDataReader reader = GetSomeReaderForGetAll...(id))
        {
            while (reader.Read())
            {
              Product product = new Product();
              ((IRecord)product).Read(reader);
              products.Add(product);
            }
        }
        return products;
    }
}

// an interface to read from a data record (possibly platform independent)
public interface IRecord
{
  void Read(IDataReader reader);
}
于 2011-02-28T21:17:15.703 に答える
2

Cade は適切な説明をしています。Anemic ドメイン モデルを回避するには、次のことを検討してください。

  • DTOオブジェクトをドメインオブジェクトにします(単に「製品」と呼びます)
  • IsValidProductId は製品上にある可能性があり、セッターが呼び出されたときに有効であることを確認し、有効でない場合はスローできます
  • 名前に関する何らかの規則を実装する
  • Product と相互作用するオブジェクトが他にある場合は、もっと興味深いことについて話すことができます。
于 2011-02-28T20:35:26.317 に答える
1

ORM の使用について他の人が言ったこと - モデルが拡大するにつれて、ORM がないと多くのコードの繰り返しが発生します。しかし、あなたの「5,000 はどうですか」という質問についてコメントしたいと思います。

クラスをコピーしても、そのメソッドのコピーが 5,000 個作成されるわけではありません。データ構造のコピーを作成するだけです。ドメイン オブジェクトにビジネス ロジックを配置しても、効率が低下することはありません。一部のビジネス ロジックが適用できない場合は、特定の目的のためにオブジェクトを装飾するサブクラスを作成できますが、これの目的は、効率ではなく、意図した用途に一致するオブジェクトを作成することです。貧血症の設計モデルは効率的ではありません。

また、アプリケーションでデータをどのように使用するかについても検討してください。おそらく参照リストを除いて、「GetAllOfSomething()」のようなメソッドを使用したことは一度もありません。データベース内のすべてを取得する目的は何ですか? 何らかのプロセス、データ操作、レポートを行う場合は、そのプロセスを実行するメソッドを公開する必要があります。グリッドへの入力など、外部で使用するためにリストを公開する必要がある場合は、 を公開し、IEnumerableデータをサブセット化するためのメソッドを提供します。メモリ内のデータの完全なリストを操作するという考えから始めると、データが大きくなるにつれて深刻なパフォーマンスの問題が発生します。

于 2011-02-28T21:01:16.410 に答える
1

貧血ドメインとは、製品または他のクラスが実際にはデータ セッターとゲッター以上のものを実装していない場合であり、ドメインの動作はありません。

たとえば、製品ドメイン オブジェクトには、公開されているいくつかのメソッド、いくつかのデータ検証、いくつかの実際のビジネス ロジックが必要です。

それ以外の場合、BLL バージョン (ドメイン オブジェクト) は DTO よりも優れているとは言えません。

http://martinfowler.com/bliki/AnemicDomainModel.html

ProductBLL productBll = new ProductBLL();
List<ProductDTO> productList = productBll.GetAllProducts();

ここでの問題は、モデルが貧弱であると前提しており、DTO をビジネス層のコンシューマー (UI など) に公開していることです。

通常、アプリケーション コード<Product>は、BLL や DTO などではなく、s を操作する必要があります。それらは実装クラスです。それらは、アプリケーション プログラマー レベルの思考にとってほとんど意味がないだけでなく、表面上は問題のドメインを理解しているドメインの専門家にとってもほとんど意味がありません。したがって、私が言いたいことを理解しているなら、バスルームを設計しているときではなく、配管に取り組んでいるときにのみ見えるようにする必要があります.

BLL オブジェクトにビジネス ドメイン エンティティの名前を付けます。また、DTO はビジネス エンティティと DAL の間の内部にあります。ドメイン エンティティが DTO 以外のことを何もしない場合 - それは貧血です。

また、私はしばしば明示的な DTO クラスを省略し、ドメイン オブジェクトを構成で定義された整理されたストアド プロシージャを持つ汎用 DAL に移動させ、それ自体を単純な古いデータリーダーからそのプロパティにロードすることを追加します。クロージャーを使用すると、パラメーターを挿入できるコールバックを備えた非常に汎用的な DAL を使用できるようになりました。

私はおそらくうまくいく最も簡単なことに固執します:

public class Product {
    // no one can "make" Products
    private Product(IDataRecord dr) {
        // Make this product from the contents of the IDataRecord
    }

    static private List<Product> GetList(string sp, Action<DbCommand> addParameters) {
        List<Product> lp = new List<Product>();
        // DAL.Retrieve yields an iEnumerable<IDataRecord> (optional addParameters callback)
        // public static IEnumerable<IDataRecord> Retrieve(string StoredProcName, Action<DbCommand> addParameters)
        foreach (var dr in DAL.Retrieve(sp, addParameters) ) {
            lp.Add(new Product(dr));
        }
        return lp;
    }

    static public List<Product> AllProducts() {
        return GetList("sp_AllProducts", null) ;
    }

    static public List<Product> AllProductsStartingWith(string str) {
        return GetList("sp_AllProductsStartingWith", cm => cm.Parameters.Add("StartsWith", str)) ;
    }

    static public List<Product> AllProductsOnOrder(Order o) {
        return GetList("sp_AllProductsOnOrder", cm => cm.Parameters.Add("OrderId", o.OrderId)) ;
    }
}

次に、明らかな部分を DAL に移動できます。DataRecords は DTO として機能しますが、存続期間は非常に短く、それらのコレクションは実際には存在しません。

これは、静的な SqlServer の DAL.Retrieve です (CommandText を使用するように変更するのは簡単であることがわかります)。接続文字列をカプセル化するバージョンがあります(したがって、静的メソッドではありません)。

    public static IEnumerable<IDataRecord> SqlRetrieve(string ConnectionString, string StoredProcName,
                                                       Action<SqlCommand> addParameters)
    {
        using (var cn = new SqlConnection(ConnectionString))
        using (var cmd = new SqlCommand(StoredProcName, cn))
        {
            cn.Open();
            cmd.CommandType = CommandType.StoredProcedure;

            if (addParameters != null)
            {
                addParameters(cmd);
            }

            using (var rdr = cmd.ExecuteReader())
            {
                while (rdr.Read())
                    yield return rdr;
            }
        }
    }

後で本格的なフレームワークに進むことができます。

于 2011-02-28T20:27:00.850 に答える