【问题标题】:Entity Framework, Code First, Update "one to many" relationship with independent associations实体框架,代码优先,更新与独立关联的“一对多”关系
【发布时间】:2011-08-07 10:26:30
【问题描述】:

我花了很长时间才找到下面描述的场景的解决方案。看似简单的事情,却被证明是相当困难的。问题是:

使用 Entity Framework 4.1(代码优先方法)和“独立关联”如何在“分离”场景(在我的情况下为 Asp.Net)中为现有的“多对一”关系分配不同的端。

型号:

我意识到使用 ForeignKey 关系而不是独立关联是一种选择,但我更喜欢在我的 Pocos 中没有 ForeignKey 实现。

一位客户有一个或多个目标:

    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; }
    }
}

一个目标有一个客户:

    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;
    }
}

【问题讨论】:

  • 我没有足够的推荐积分在接下来的 8 小时内发布答案。将尽快发布。
  • 这非常具有挑战性,我正在撰写有关此问题的整篇博客文章(我几乎完成了两个月,但我仍然对此并不完全满意)。在多方面更新一对多关系可能是实现外键关联的主要原因(而不是修复这种可怕的行为)。
  • 我同意。这个过程感觉肯定比它应该的更困难。尽管如此,EF 4 和 4.1 仍然比以前的版本有了显着的改进。围绕 EF 4.1 研究主题很困难,因为 CTP 和发布版本之间的 API 变化、分离与连接场景中的不同方法、许多过度简化的示例应用程序没有为 n 带来的额外复杂性提供指导层级解决方案...

标签: entity-framework associations code-first dbcontext


【解决方案1】:

我等待@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 内部的状态模型和状态验证。当没有其他处于删除状态时,具有强制关系(在多方面)的处于未更改或已修改状态的实体在添加状态下不能具有独立关联。根本不允许修改关联状态。

【讨论】:

  • 谢谢你的回答,拉迪斯拉夫。您的解决方案有效,我更喜欢它而不是我的解决方案,因为您的解决方案不需要使用 ObjectContext API。 public void UpdateTarget(Target target, Target origTarget) { try { _tgrDbContext.Targets.Attach(origTarget); _tgrDbContext.Entry(origTarget).Reload(); _tgrDbContext.Customers.Attach(target.Customer); _tgrDbContext.Entry(origTarget).CurrentValues.SetValues(target); origTarget.Customer = 目标.客户; _tgrDbContext.Entry(origTarget).State = EntityState.Modified; _tgrDbContext.SaveChanges(); }
  • 更喜欢@Martin 的解决方案。如果我们需要替换 1->1 rela'ship,您可以运行 Martins 代码而忽略删除代码部分。只需要“EntityState.Added”行。益处?您不需要知道之前的子对象。在我的情况下,这是一个非常可观的收益。
【解决方案2】:

关于这个主题有很多信息可以找到;在 stackoverflow 上,我发现 Ladislav Mrnka 的见解特别有用。更多关于这个主题的信息也可以在这里找到:NTier Improvements for Entity Framework 和这里What's new in Entity Framework 4?

在我的项目(Asp.Net Webforms)中,用户可以选择用不同的(现有)客户对象替换分配给目标对象的客户。此事务由绑定到 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 方法。值得注意的是,插入新目标对象的过程要容易得多。 DbContext 正确识别独立关联的客户端,并毫不费力地将更改提交到数据库。存储库类中的 Insert 方法如下所示:

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

希望这对处理类似任务的人有用。如果您发现上述这种方法存在问题,请在您的 cmets 中告诉我。谢谢!

【讨论】:

    猜你喜欢
    • 2015-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多