0

次のようなコレクションプロパティを持つエンティティがあります。

public class MyEntity
{
    public virtual ICollection<OtherEntity> Others { get; set; }
}

データコンテキストまたはリポジトリを介してこのエンティティを取得する場合、を使用して他のユーザーがこのコレクションにアイテムを追加しないようにしますMyEntity.Others.Add(entity)。これは、エンティティをコレクションに追加する前に、検証コードを実行したい場合があるためです。私はこのMyEntityような方法を提供することによってこれを行います:

public void AddOther(OtherEntity other)
{
    // perform validation code here

    this.Others.Add(other);
}

私はこれまでにいくつかのことをテストしましたが、最終的に到達したのはこのようなものです。エンティティにコレクションを作成し、private公開すると次のpublic ReadOnlyCollection<T>ようになりMyEntityます。

public class MyEntity
{
    private readonly ICollection<OtherEntity> _others = new Collection<OtherEntity>();

    public virtual IEnumerable<OtherEntity>
    {
        get
        {
            return _others.AsEnumerable();
        }
    }
}

これは私が探しているもののようで、単体テストは問題なく合格していますが、まだ統合テストを開始していないので、疑問に思っています

  1. 私が探しているものを達成するためのより良い方法はありますか?
  2. このルートを進むことにした場合(可能であれば)、私が直面する影響は何ですか?

いつも助けてくれてありがとう。

編集1をからに変更し、ゲッターとして使用ReadOnlyCollectionしています。ユニットテストは問題なく合格しますが、統合中に直面する問題がわからないため、EFは関連するエンティティを使用してこれらのコレクションの構築を開始します。IEnumerablereturn _others.AsEnumerable();

編集2そこで、内部コレクションに追加する前に、提供されたエンティティに対してメソッドが検証を実行する場所をValidatableCollection実装する派生コレクション(それを呼び出す)を作成する提案を試すことにしました。残念ながら、Entity Frameworkは、ナビゲーションプロパティを構築するときにこのメソッドを呼び出すため、実際には適していません。ICollection.Add()

4

6 に答える 6

2

この目的のために、コレクション クラスを作成します。

OtherEntityCollection : Collection<OtherEntity>
{
    protected override void InsertItem(int index, OtherEntity item)
    {
        // do your validation here
        base.InsertItem(index, item);
    }

    // other overrides
}

この検証をバイパスする方法がないため、これはより厳格になります。ドキュメントでより複雑な例を確認できます。

よくわからないことの 1 つは、データベースからデータを具体化するときに EF にこの具象型を作成させる方法です。しかし、ここに見られるように、おそらく実行可能です。

編集: 検証をエンティティ内に保持したい場合は、エンティティが実装するカスタム インターフェイスと、このインターフェイスを呼び出す汎用コレクションを介してジェネリックにすることができます。

EF の問題点としては、EF がコレクションを再実体化するときにAdd、各アイテムを呼び出すことが最大の問題になると思います。次に、項目がビジネス ルールとしてではなく、インフラストラクチャの動作として "追加" された場合でも、検証が呼び出されます。これにより、奇妙な動作やバグが発生する可能性があります。

于 2013-01-10T11:00:01.963 に答える
1

に戻ることをお勧めしReadOnlyCollection<T>ます。過去に同様のシナリオで使用しましたが、問題はありませんでした。

さらに、AsEnumerable()参照の型を変更するだけで、新しい独立したオブジェクトを生成しないため、このアプローチは機能しません。

MyEntity m = new MyEntity();
Console.WriteLine(m.Others.Count()); //0
(m.Others as Collection<OtherEntity>).Add(new OtherEntity{ID = 1});
Console.WriteLine(m.Others.Count()); //1

プライベート コレクションに正常に挿入されます。

于 2013-01-10T11:00:11.933 に答える
1

コレクションはにキャストすることで簡単に変更できるため、AsEnumerable()onを使用しないでください。HashSetICollection<OtherEntity>

var values = new MyEntity().Entities;
((ICollection<OtherEntity>)values).Add(new OtherEntity());

次のようなリストのコピーを返してみてください

return new ReadOnlyCollection<OtherEntity>(_others.ToList()).AsEnumerable();

これにより、ユーザーが変更しようとすると、例外が確実に発生します。ユーザーの明確さと利便性のために、ReadOnlyCollection代わりに戻り値の型として公開できます。IEnumerable.NET 4.5 では、新しいインターフェイスが追加されましIReadOnlyCollectionた。

一部のコンポーネントが List ミューテーションに依存することを除いて、大きな統合の問題は発生しません。ユーザーが ToList または ToArray を呼び出すと、コピーが返されます。

于 2013-01-10T11:00:15.000 に答える
1

ここには 2 つのオプションがあります。

1) 現在使用している方法: コレクションを として公開ReadOnlyCollection<OtherEntity>し、クラスにメソッドを追加しMyEntityてそのコレクションを変更します。これはまったく問題ありませんが、 のコレクションを使用OtherEntityするクラスにのコレクションの検証ロジックを追加することを考慮してください。そのため、プロジェクトの他の場所で のコレクションを使用する場合は、おそらく検証コードを複製する必要があります。 、それはコードの匂いです (DRY) :POtherEntity

2)それを解決するための最良の方法は、OtherEntityCollection実装するカスタムクラスを作成して、ICollection<OtherEntity>そこに検証ロジックを追加できるようにすることです。コレクション操作を実際に実装するインスタンスを含む単純な OtherEntityCollection オブジェクトを作成できるためList<OtherEntity>、挿入を検証するだけで済みます。

編集:複数のエンティティのカスタム検証が必要な場合は、その検証を実行する他のオブジェクトを受け取るカスタム コレクションを作成する必要があります。以下の例を変更しましたが、ジェネリック クラスを作成するのは難しくありません。

class OtherEntityCollection : ICollection<OtherEntity>  
{
  OtherEntityCollection(Predicate<OtherEntity> validation)
  {
    _validator = validator;
  } 

  private List<OtherEntity> _list = new List<OtherEntity>();
  private Predicate<OtherEntity> _validator;
  public override void Add(OtherEntity entity)   
  {
     // Validation logic
     if(_validator(entity))
       _list.Add(entity);   
  }
}
于 2013-01-10T11:01:08.240 に答える
0

最後に、適切な実用的な解決策があります。これが私がしたことです。私は、教師が処理できるよりも多くの生徒を教えるのをやめたいなど、より読みやすいものに変更MyEntityします。OtherEntityTeacherStudent

最初に、次のように呼ばれる、この方法で検証する予定のすべてのエンティティのインターフェイスを作成しましたIValidatableEntity

public interface IValidatableEntity
{
    void Validate();
}

Student次に、のコレクションに追加するときにこのエンティティを検証しているため、このインターフェイスを自分に実装しますTeacher

public class Student : IValidatableEntity
{
    public virtual Teacher Teacher { get; set; }

    public void Validate()
    {
        if (this.Teacher.Students.Count() > this.Teacher.MaxStudents)
        {
            throw new CustomException("Too many students!");
        }
    }
}

次に、validateを呼び出す方法について説明します。エンティティコンテキストをオーバーライド.SaveChanges()して、追加されたすべてのエンティティのリストを取得し、validateを呼び出すたびに、失敗した場合は、コレクションに追加されないように状態を分離に設定します。エラーメッセージとして例外(現時点ではまだ不明な点)を使用throwしているため、スタックトレースを保持するために例外を使用します。

public override int SaveChanges()
{
    foreach (var entry in ChangeTracker.Entries())
    {
        if (entry.State == System.Data.EntityState.Added)
        {
            if (entry.Entity is IValidatableEntity)
            {
                try
                {
                    (entry.Entity as IValidatableEntity).Validate();
                }
                catch
                {
                    entry.State = System.Data.EntityState.Detached;

                    throw; // preserve the stack trace
                }
            }
        }
    }

    return base.SaveChanges();
}

これは、検証コードをエンティティ内にうまく収納しておくことを意味します。これにより、単体テスト中にPOCOをモックするときに私の生活がずっと楽になります。

于 2013-01-10T16:23:15.057 に答える
0

EF はセッターなしでプロパティをマップできません。または、private set { }いくつかの構成が必要です。モデルを POCO として保持し、DTO のようなプレーン オールド

一般的なアプローチは、保存する前にモデルに対する検証ロジックを含む個別のサービス レイヤーを作成することです。

サンプル用..

public void AddOtherToMyEntity(MyEntity myEntity, OtherEntity otherEntity)
{
    if(myService.Validate(otherEntity)
    {
      myEntity.Others.Add(otherEntity);
    }
    //else ...
}

ps。コンパイラが何かをするのを防ぐことができますが、他のコーダーはできません。「検証に合格するまで、Entity Collectionを直接変更しないでください」と明示的にコードを作成しました

于 2013-01-10T11:21:02.150 に答える