2

他のリポジトリで使用されるGenericEFRepositoryを作成しようとしています。私は以下のようにSaveメソッドを持っています。

public virtual void Save(T entity) // where T : class, IEntity, new() And IEntity enforces long Id { get; set; }
{
    var entry = _dbContext.Entry(entity);

    if (entry.State != EntityState.Detached)
        return; // context already knows about entity, don't do anything

    if (entity.Id < 1)
    {
        _dbSet.Add(entity);
        return;
    }

    var attachedEntity = _dbSet.Local.SingleOrDefault(e => e.Id == entity.Id);
    if (attachedEntity != null)
        _dbContext.Entry(attachedEntity).State = EntityState.Detached;
    entry.State = EntityState.Modified;
}

あなたは以下のコードのコメントで問題を見つけることができます

 using (var uow = ObjectFactory.GetInstance<IUnitOfWork>()) // uow is implemented like EFUnitOfWork which gives the DbContext instance to repositories in GetRepository
 {
    var userRepo = uow.GetRepository<IUserRepository>();

    var user = userRepo.Get(1);
    user.Name += " Updated";

    userRepo.Save(user);
    uow.Save(); // OK only the Name of User is Updated 
 }

 using (var uow = ObjectFactory.GetInstance<IUnitOfWork>())
 {
    var userRepo = uow.GetRepository<IUserRepository>();

    var user = new User 
    {
        Id = 1,
        Name = "Brand New Name"
    };

    userRepo.Save(user);
    uow.Save();

    // NOT OK
    // All fields (Name, Surname, BirthDate etc.) in User are updated
    // which causes unassigned fields to be cleared on db
 }

私が考えることができる唯一の解決策は、のようなリポジトリを介してエンティティを作成することでuserRepo.CreateEntity(id: 1)あり、リポジトリはDbContextに接続されているエンティティを返します。しかし、これはエラーが発生しやすいようです。それでも、開発者はnewキーワードを使用してエンティティを作成できます。

この特定の問題についてのあなたの解決策の提案は何ですか?

注:GenericRepositoryとIEntityインターフェイスを使用することの短所と長所についてはすでに知っています。したがって、「GenericRepositoryを使用しない、IEntityを使用しない、すべてのエンティティに長いIDを設定しない、実行しようとしていることを実行しない」というコメントは役に立ちません。

4

3 に答える 3

6

はい、エラーが発生しやすいですが、単にそれが EF とリポジトリの問題です。更新するデータを設定する前にエンティティを作成してアタッチする必要があります(Nameあなたの場合)。または、エンティティ全体ではなく、永続化する各プロパティの変更された状態を設定する必要があります(想像できるように、開発者は忘れることができます)それ)。

最初の解決策は、リポジトリでこれを行う特別な方法につながります。

public T Create(long id) {
    T entity = _dbContext.Set<T>().Create();
    entity.Id = id;
    _dbContext.Set<T>().Attach(entity);
    return entity;
}

2番目のソリューションには次のようなものが必要です

public void Save(T entity, params Expression<Func<T, TProperty>>[] properties) {

    ...

    _dbContext.Set<T>().Attach(entity);
    if (properties.Length > 0) {
        foreach (var propertyAccessor in properties) {
            _dbContext.Entry(entity).Property(propertyAccessor).IsModified = true;
        }
    } else {
        _dbContext.Entry(entity).State = EntityState.Modified;
    }
}

そして、あなたはそれを次のように呼び出します:

userRepository(user, u => u.Name);
于 2013-01-20T20:51:52.697 に答える
1

他の2つの回答は、おそらくこの問題を回避する方法についての良い洞察を提供しますが、いくつか指摘する価値があると思います.

  • あなたがやろうとしていること(つまり、プロキシエンティティの更新)は非常にEF中心であり、IMOは実際にはEFコンテキストの外では意味がないため、汎用リポジトリがこのように動作すると予想されることは意味がありません。
  • いくつかのフィールドが既に設定されているオブジェクトをアタッチすると、値を変更するか、変更されたフラグを設定しない限り、EF は現在の DB 状態であると伝えた内容を簡潔にします。選択せずに試みていることを行うには、通常、名前なしでオブジェクトを添付し、ID オブジェクトを添付した後に名前を設定します。
  • あなたのアプローチは通常、パフォーマンス上の理由で使用されます。既存のフレームワークの上に抽象化することにより、ほとんどの場合、論理的なパフォーマンスが低下することをお勧めします。これが大したことである場合、リポジトリを使用するべきではないでしょうか? パフォーマンスの問題に対応するためにリポジトリに追加すればするほど、リポジトリはより複雑になり、制限が厳しくなり、複数の実装を提供することが難しくなります。

そうは言っても、この特定のケースは一般的な状況で処理できると思います。

これはあなたがそれを行うことができる1つの可能な方法です

public void UpdateProperty(Expression<Func<T,bool>> selector, FunctionToSetAProperty setter/*not quite sure of the correct syntax off the top of my head*/)
{
   // look in local graph for T and see if you have an already attached version
   // if not attach it with your selector value set
   // set the property of the setter
}

これが意味をなすことを願っています。私は自分の開発ボックス atm のそばにいないので、実際に動作するサンプルを作成することはできません。

この同じ動作を複数の異なる方法で実装できるため、これは汎用リポジトリのより良いアプローチだと思います.abovcはEFで機能する可能性がありますが、メモリ内リポジトリがある場合は異なる方法があります(たとえば)。このアプローチにより、リポジトリを EF のようにのみ動作するように制限するのではなく、目的を満たすさまざまな実装を実装できます。

于 2013-01-21T07:36:14.983 に答える
1

変更したフィールドと変更しなかったフィールドをリポジトリが魔法のように認識することを期待しているため、これはこのアプローチの基本的な問題の一種です。が有効な値のnull場合、「未変更」のシグナルとして使用しても機能しません。null

string[]たとえば、フィールド名とともにa を送信するなど、書き込みたいフィールドをリポジトリに伝える必要があります。または、フィールドごとに 1 つのブール値。これは良い解決策だとは思いません。

おそらく、次のように制御フローを反転できます。

var entity = repo.Get(1);
entity.Name += "x";
repo.SaveChanges();

これにより、変更追跡が機能するようになります。これは、 EFの使用方法に近いものです。

別:

var entity = repo.Get(1);
entity.Name += "x";
repo.Save(entity);
于 2013-01-20T20:49:42.093 に答える