0

EF5 Code-First アプローチを使用した最も単純化された形式の状況を次に示します。

public abstract class EntityBase<PK>
{
    public PK ID { get; set; }
}
public class Country : EntityBase<string>
{
    public string Name { get; set; }
}
public class Address : EntityBase<int>
{
    [Required]
    public string CountryID { get; set; }
    public Country Country { get; set; }
    // ... other address properties ...
}

と の間の 1 対多の関係はAddressCountry次のようにカスケード削除なしで設定されます。

modelBuilder.Entity<Address>()
    .HasRequired(a => a.Country)
    .WithMany()
    .HasForeignKey(a => a.CountryID)
    .WillCascadeOnDelete(false);

SaveChanges最後に、基になる DbContext を呼び出してデータの変更をアトミックにコミットする CRUD メソッドを備えた汎用ベース リポジトリ クラスがあります。例えば:

public class EFRepository<T, PK> : IRepository<T, PK> where T : EntityBase<PK>
{
    //
    // ... other methods ... 
    //
    public virtual void Delete(T instance)
    {
        // ... trigger validations, write to log, etc...
        _dbContext.Set<T>().Remove(instance);
        try
        {
            _dbContext.SaveChanges();
        }
        catch(Exception ex)
        {
            // ... handle the error ...
        }
    }
}

パート1:

シナリオ:

var countryRepo = new EFRepository<Country>();
var country = countryRepo.Save(new Country() { ID="??", Name="Test Country" });

var addressRepo = new EFRepository<Address>();
var address = addressRepo.Save(new Address() { Country=country });

countryRepo.Delete(country);

これは依存関係が存在するため失敗するはずAddressです。ただし、その後、アドレスは null in で終了しますCountryID。これは が必要なため無効です。そのため、アドレスが分離されていない限り、Address.CountryID後続のSaveChanges呼び出しで検証例外がスローされます。

オブジェクトが削除されると、EF5 は最初に上記のようなカスケード削除制約をチェックし、見つからない場合はデータの削除に進むほどスマートになると予想していました。しかし、まったく逆の場合があるようです。

これは正常な動作ですか、それとも何か間違ったことをしていますか?

パート2:

SaveChanges呼び出しが失敗した後、一部Addressesは DbContext で無効な状態になり、元の値に復元する必要があります。もちろん、専用のリポジトリ クラスを作成して をオーバーライドすることで、いつでも各エンティティ タイプ ( CountryState、など) に対して明示的にこれを行うことができますが、それには時間がかかります。呼び出しが失敗した後、関連するエンティティを適切に回復するための汎用コードを書きたいと思い ます。OrderDeleteSaveChanges

Countryクラスが依存エンティティへのナビゲーション プロパティを定義しているかどうかに関係なく、エンティティ (例: ) がプリンシパルであるすべての関係を取得するには、DbContext に問い合わせる必要があります。

たとえば、プロパティCountryがないため、DbContext でとAddressesの間の 1 対多の関係の定義を何らかの方法で見つけ、それを使用して元の値に関連するすべてを復元する必要があります。CountryAddressAddresses

これは可能ですか?

4

1 に答える 1

0

パート 2で自分の質問に答える:

これは、多対 1 の関係のプリンシパル側でエンティティを削除し、依存関係がプリンシパルのナビゲーション コレクションとして公開されていない場合 (たとえば、クラスAddressにはCountryプロパティがありますが、クラスにCountryはありません)、関連する依存関係をチェックする私のアプローチです。コレクションがありAddressesます)。

DbContext

次のメソッドをコンテキスト クラスに追加します。

/// <summary>
/// Returns an array of entities tracked by the 
/// context that satisfy the filter criteria.
/// </summary>
public DbEntityEntry[] GetTrackedEntities<T>(
    Expression<Func<DbEntityEntry<T>, bool>> filterCriteria) 
    where T : class
{
    var result = new List<DbEntityEntry>();
    var doesItMatch = filterCriteria.Compile();

    foreach (var entry in this.ChangeTracker.Entries<T>())
    {
        if (doesItMatch(entry))
            result.Add(entry);
    }
    return result.ToArray();
}

リポジトリ

いくつかの依存関係を持つ各クラスのリポジトリを作成し、メソッドをオーバーライドしDelete、新しいGetTrackedEntities<T>メソッドを使用して関連するすべての依存関係を取得し、次のいずれかを行います。

  • コードでカスケード削除可能な場合は、明示的に削除します
  • それらがDB自体でカスケード削除可能である場合、それらをコンテキストから切り離します
  • それらがカスケード削除可能でない場合、例外をスローします。

後者の例:

public class EFCountryRepository : 
    EFReadWriteRepository<Country, string>, 
    ICountryRepository 
{
    public override void Delete(Country instance)
    {
        // Allow the Country to be deleted only if there are no dependent entities
        // currently in the context that are NOT cascade-deletable.
        if (
            // are there any Regions in the context that belong to this Country?
            _dbContext.GetTrackedEntities<Region>(e => 
                e.Entity.CountryID == instance.ID || 
                e.Entity.Country == instance).Length > 0  
            ||
            // are there any Addresses in the context that belong to this Country?
            _dbContext.GetTrackedEntities<Address>(e =>
                e.Entity.CountryID == instance.ID || 
                e.Entity.Country == instance).Length > 0
        )
            throw new Exception(String.Format(
                "Country '{0}' is in use and cannot be deleted.", instance.ID));

        base.Delete(instance);
    }
    // ... other methods ...
}

カスケード削除が DB 自体によって行われる場合の例です。そのため、依存関係をコンテキストから切り離すだけで済みます。

public class EFOrderRepository : 
    EFReadWriteRepository<Order, string>, 
    IOrderRepository 
{
    public override void Delete(Order instance)
    {
        foreach (var orderItem in _dbContext.GetTrackedEntities<OrderItem>(e => 
                e.Entity.OrderID == instance.ID || 
                e.Entity.Order == instance))
        {
            _dbContext.Entry(orderItem).State = System.Data.EntityState.Detached;
        }
        base.Delete(instance);
    }
    // ... other methods ...
}

誰かがこのソリューションが役立つことを願っています。

于 2013-06-04T21:31:58.263 に答える