13

以下で説明するシナリオの解決策を見つけるのに時間がかかりすぎました。一見単純な出来事が、かなり難しいことが判明しました。質問は:

Entity Framework 4.1 (Code First アプローチ) と「独立した関連付け」を使用して、「切り離された」シナリオ (私の場合は Asp.Net) で、既存の「多対 1」の関係に別の端を割り当てる方法を教えてください。

モデル:

独立アソシエーションの代わりに ForeignKey リレーションシップを使用することも選択肢の 1 つだったことは理解していますが、Pocos に ForeignKey 実装を持たないことが私の好みでした。

顧客には 1 つ以上のターゲットがあります。

    public class Customer:Person
{
    public string Number { get; set; }
    public string NameContactPerson { get; set; }
    private ICollection<Target> _targets;

    // Independent Association
    public virtual ICollection<Target> Targets
    {
        get { return _targets ?? (_targets = new Collection<Target>()); }
        set { _targets = value; }
    }
}

ターゲットには 1 人の顧客がいます。

    public class Target:EntityBase
{
    public string Name { get; set; }
    public string Description { get; set; }
    public string Note { get; set; }
    public virtual Address Address { get; set; }
    public virtual Customer Customer { get; set; }
}

Customer は Person クラスから派生します。

    public class Person:EntityBase
{        
    public string Salutation { get; set; }
    public string Title { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set  ; }        
    public string Telephone1 { get; set; }
    public string Telephone2 { get; set; }
    public string Email { get; set; }        

    public virtual Address Address { get; set; }
}

EntityBase クラスは、いくつかの一般的なプロパティを提供します。

    public abstract class EntityBase : INotifyPropertyChanged
{
    public EntityBase()
    {
        CreateDate = DateTime.Now;
        ChangeDate = CreateDate;
        CreateUser = HttpContext.Current.User.Identity.Name;
        ChangeUser = CreateUser;
        PropertyChanged += EntityBase_PropertyChanged;
    }

    public void EntityBase_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (Id != new Guid())
        {
            ChangeDate = DateTime.Now;
            ChangeUser = HttpContext.Current.User.Identity.Name;
        }
    }

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, e);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public Guid Id { get; set; }
    public DateTime CreateDate { get; set; }
    public DateTime? ChangeDate { get; set; }
    public string CreateUser { get; set; }
    public string ChangeUser { get; set; }
}

コンテキスト:

    public class TgrDbContext : DbContext
{
    public DbSet<Person> Persons { get; set; }
    public DbSet<Address> Addresses { get; set; }
    public DbSet<Customer> Customers { get; set; }
    public DbSet<Target> Targets { get; set; }
    public DbSet<ReportRequest> ReportRequests { get; set; }

    // If OnModelCreating becomes to big, use "Model Configuration Classes"
    //(derived from EntityTypeConfiguration) instead
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>().HasOptional(e => e.Address);            
        modelBuilder.Entity<Customer>().HasMany(c => c.Targets).WithRequired(t => t.Customer);            
    }

    public static ObjectContext TgrObjectContext(TgrDbContext tgrDbContext)
    {           
        return ((IObjectContextAdapter)tgrDbContext).ObjectContext;
    }
}
4

2 に答える 2

5

この問題には他にも解決策があるため、@Martinの回答を待ちました。別のものを次に示します (少なくとも ObjectContext API で動作するため、DbContext API でも動作するはずです)。

// Existing customer
var customer = new Customer() { Id = customerId };
// Another existing customer
var customer2 = new Customer() { Id = customerId2 };

var target = new Target { ID = oldTargetId };
// Make connection between target and old customer
target.Customer = customer;

// Attach target with old customer
context.Targets.Attach(target);
// Attach second customer
context.Customers.Attach(customer2);
// Set customer to a new value on attached object (it will delete old relation and add new one)
target.Customer = customer2;

// Change target's state to Modified
context.Entry(target).State = EntityState.Modified;
context.SaveChanges();

ここでの問題は、EF 内の内部状態モデルと状態の検証です。必須の関係 (多くの側) を持つ変更されていない状態または変更された状態のエンティティは、削除された状態に他に存在しない場合、追加された状態に独立した関連付けを持つことはできません。関連付けのための状態の変更は一切許可されていません。

于 2011-08-07T21:20:14.957 に答える
4

このトピックに関する情報はたくさんあります。stackoverflow では、Ladislav Mrnka の洞察が特に役立つことがわかりました。この件に関する詳細は、こちら: Entity Framework のNTier の改善と、こちらの Entity Framework 4 の新機能?

私のプロジェクト (Asp.Net Webforms) では、ユーザーは、Target オブジェクトに割り当てられた Customer を別の (既存の) Customer オブジェクトに置き換えるオプションがあります。このトランザクションは、ObjectDataSource にバインドされた FormView コントロールによって実行されます。ObjectDataSource は、プロジェクトの BusinessLogic 層と通信し、DataAccess 層の Target オブジェクトのリポジトリ クラスにトランザクションを渡します。リポジトリ クラスの Target オブジェクトの Update メソッドは次のようになります。

    public void UpdateTarget(Target target, Target origTarget)
    {
        try
        {
            // It is not possible to handle updating one to many relationships (i.e. assign a 
            // different Customer to a Target) with "Independent Associations" in Code First.
            // (It is possible when using "ForeignKey Associations" instead of "Independent 
            // Associations" but this brings about a different set of problems.)
            // In order to update one to many relationships formed by "Independent Associations"
            // it is necessary to resort to using the ObjectContext class (derived from an 
            // instance of DbContext) and 'manually' update the relationship between Target and Customer. 

            // Get ObjectContext from DbContext - ((IObjectContextAdapter)tgrDbContext).ObjectContext;
            ObjectContext tgrObjectContext = TgrDbContext.TgrObjectContext(_tgrDbContext);

            // Attach the original origTarget and update it with the current values contained in target
            // This does NOT update changes that occurred in an "Independent Association"; if target
            // has a different Customer assigned than origTarget this will go unrecognized
            tgrObjectContext.AttachTo("Targets", origTarget);
            tgrObjectContext.ApplyCurrentValues("Targets", target);

            // This will take care of changes in an "Independent Association". A Customer has many
            // Targets but any Target has exactly one Customer. Therefore the order of the two
            // ChangeRelationshipState statements is important: Delete has to occur first, otherwise
            // Target would have temporarily two Customers assigned.
            tgrObjectContext.ObjectStateManager.ChangeRelationshipState(
                origTarget,
                origTarget.Customer,
                o => o.Customer,
                EntityState.Deleted);

            tgrObjectContext.ObjectStateManager.ChangeRelationshipState(
                origTarget,
                target.Customer,
                o => o.Customer,
                EntityState.Added);

            // Commit
            tgrObjectContext.Refresh(RefreshMode.ClientWins, origTarget);
            tgrObjectContext.SaveChanges();
        }
        catch (Exception)
        {
            throw;
        }
    }            

これは、Target オブジェクトの Update メソッドに対して機能します。驚くべきことに、新しい Target オブジェクトを挿入する手順ははるかに簡単です。DbContext は、独立したアソシエーションの Customer 側を適切に認識し、手間をかけずに変更をデータベースにコミットします。リポジトリ クラスの Insert メソッドは次のようになります。

        public void InsertTarget(Target target)
    {
        try
        {
            _tgrDbContext.Targets.Add(target);
            _tgrDbContext.SaveChanges();
        }
        catch (Exception)
        {
            throw;
        }
    }

うまくいけば、これは誰かが同様のタスクを扱うのに役立つでしょう. 上記のこのアプローチに問題がある場合は、コメントでお知らせください。ありがとう!

于 2011-08-07T20:24:51.477 に答える