EF(核心):具有附加关系的修改后实体

时间:2019-01-28 10:57:45

标签: c# entity-framework entity-framework-core

假设我有一个实体,该实体具有一对多导航到另一个实体的功能。例如:

public class Person {

    public int Id {get; set;}
    public String Name {get; set;}
    public ICollection<PhoneNumber> PhoneNumbers {get; set;}
    ...
}
public class PhoneNumber {
   public int Id {get; set;}
   public String number {get; set;}
   public int PersonId {get; set;}
   public Person PersonNavigation {get; set;}
}

如果我想更新此人的姓名并同时在该人的电话号码中添加一些号码或删除一些号码,例如:

Person person = db.Set<Person>().SingleOrDefault(x=> x.Id == 1);
person.Name = "James";
deleteOneNumber = person.PhoneNumbers.FirstOrDefault(x=> x.number == "555");
person.PhoneNumbers.Remove(deleteOneNumber);
foreach(var number in new String[] {"123","456"}){
   person.PhoneNumbers.Add(new PhoneNumber{ Number = i});
}

我应该如何精确定义框架以应用所有更改?根据EF Core文档,Update方法会将所有实体的状态更改为Modified,而Add将这些实体的状态更改为Added。如果我在这里使用Modify方法,则对于数据库中不存在新添加的数字的事实,无法对其进行修改。

编辑:

假设这是一个断开连接的场景。

2 个答案:

答案 0 :(得分:1)

虽然年代久远,但在发布自己的问题时偶然发现了这一点,所以我会尝试一下。

首先,我不确定从PhoneNumber到Person的关系是否有效,是否确实忽略了这部分,如果不能,则检查如何使用自定义外键而不是标准外键(PersonNavigationId / PersonNavigation )。

public class PhoneNumber {
   public int Id {get; set;}
   public String number {get; set;}

   public int PersonId {get; set;}
   [ForeignKey(nameof(PersonId))]
   public Person PersonNavigation {get; set;}
}

如果您不使用延迟加载,则由于未跟踪您的实体(我也看不到任何更新/保存调用)而导致代码无法正常工作,EF无法使用您拥有的相似代码检测到更改以上。为了使EF能够检测到那些更改并轻松地更新相关实体,您需要让它知道它应该跟踪关系,您可以通过包括(渴望加载)相关实体来做到这一点。如果您的示例是DbContext,我假设该数据库。

Person person = db.Set<Person>().Include(p => p.PhoneNumbers).SingleOrDefault(x=> x.Id == 1);

person.Name = "James";

deleteOneNumber = person.PhoneNumbers.FirstOrDefault(x=> x.number == "555");
person.PhoneNumbers.Remove(deleteOneNumber);

foreach(var number in new String[] {"123","456"}){
   person.PhoneNumbers.Add(new PhoneNumber{ number = i});
}

db.Set<Person>().Update(person);
db.SaveChanges();

但是,我更喜欢这样的东西,其中大多数是语法,但是async / await在等待IO完成时释放了线程。假定您已遵循DbContext的标准命名约定。

var person = await db.Persons.Include(p => p.PhoneNumbers).FirstOrDefaultAsync(p => p.Id == 1);

person.Name = "James";

deleteOneNumber = person.PhoneNumbers.FirstOrDefault(pn => pn.number == "555");
person.PhoneNumbers.Remove(deleteOneNumber);

foreach(var number in new String[] {"123","456"}){
   person.PhoneNumbers.Add(new PhoneNumber{ number = i});
}

db.Persons.Update(person);
await db.SaveChangesAsync();

答案 1 :(得分:0)

我使用TrackGraph解决类似问题的方法

简介

我只想修改实体的几列以及添加\修改嵌套的子实体

  • 在这里,我正在更新Scenario实体并仅修改ScenarioDate
  • 我正在其子实体(即导航属性TempScenario)中添加一条新记录
  • 在嵌套子实体Scenariostation中,我还要添加和修改记录
public partial class Scenario
{
    public Scenario()
    {
        InverseTempscenario = new HashSet<Scenario>();
        Scenariostation = new HashSet<Scenariostation>();
    }
    public int Scenarioid { get; set; }
    public string Scenarioname { get; set; }
    public DateTime? Scenariodate { get; set; }
    public int Streetlayerid { get; set; }
    public string Scenarionotes { get; set; }
    public int? Modifiedbyuserid { get; set; }
    public DateTime? Modifieddate { get; set; }
    public int? Tempscenarioid { get; set; }

    
    public virtual Scenario Tempscenario { get; set; }
    public virtual ICollection<Scenario> InverseTempscenario { get; set; }
    public virtual ICollection<Scenariostation> Scenariostation { get; set; }
}


public partial class Scenariostation
{
    public Scenariostation()
    {
        Scenariounit = new HashSet<Scenariounit>();
    }

    public int Scenariostationid { get; set; }
    public int Scenarioid { get; set; }
    public int Stationid { get; set; }
    public bool? Isapplicable { get; set; }
    public int? Createdbyuserid { get; set; }
    public int? Modifiedbyuserid { get; set; }
    public DateTime? Modifieddate { get; set; }
    
    public virtual Scenario Scenario { get; set; }
    public virtual Station Station { get; set; }
}

public partial class Station
{
    public Station()
    {
        Scenariostation = new HashSet<Scenariostation>();
    }

    public int Stationid { get; set; }
    public string Stationname { get; set; }
    public string Address { get; set; }
    public NpgsqlPoint? Stationlocation { get; set; }
    public int? Modifiedbyuserid { get; set; }
    public DateTime? Modifieddate { get; set; }

    public virtual ICollection<Scenariostation> Scenariostation { get; set; }
}
  • 使用EF Core,如果您不想进行2次数据库往返,那么在断开连接的情况下进行数据更新将非常棘手。

  • 尽管2次数据库访问似乎并不重要,但如果数据表具有数百万条记录,它可能会影响性能。

  • 此外,如果只有几列要更新,包括嵌套子实体的列,则Usual Approach将不起作用

常用方法

public virtual void Update(T entity)
{
    if (entity == null)
        throw new ArgumentNullException("entity");

    var returnEntity = _dbSet.Attach(entity);
    _context.Entry(entity).State = EntityState.Modified;
}

但是,如果您使用此DbContext.Entry(entity).EntityState = EntityState.IsModified,则会断开EF Core更新的连接,这将更新所有列。 因此某些列将更新为默认值,即null或默认数据类型值。

此外,由于实体状态为ScenarioStation,因此UnChanged的某些记录将根本不会更新。

因此,为了仅更新从客户端发送的列,需要以某种方式告知 EF Core

使用ChangeTracker.TrackGraph

最近,我发现了这种DbConetxt.ChangeTracker.TrackGraph方法,该方法可用于标记实体的AddedUnChanged状态。

差异是,使用TrackGraph,您可以添加自定义逻辑,从而在实体的“导航”属性中迭代导航。

我使用TrackGraph的自定义逻辑

public virtual void UpdateThroughGraph(T entity, Dictionary<string, List<string>> columnsToBeUpdated)
{
    if (entity == null)
        throw new ArgumentNullException("entity");

    _context.ChangeTracker.TrackGraph(entity, e =>
    {
        string navigationPropertyName = e.Entry.Entity.GetType().Name;

        if (e.Entry.IsKeySet)
        {
            e.Entry.State = EntityState.Unchanged;
            e.Entry.Property("Modifieddate").CurrentValue = DateTime.Now;

            if (columnsToBeUpdated.ContainsKey(navigationPropertyName))
            {
                foreach (var property in e.Entry.Properties)
                {
                    if (columnsToBeUpdated[e.Entry.Entity.GetType().Name].Contains(property.Metadata.Name))
                    {
                        property.IsModified = true;
                    }
                }
            }
        }
        else
        {
            e.Entry.State = EntityState.Added;
        }

    });

}

通过这种方法,我能够轻松处理任何嵌套子实体及其列的所需的列更新以及新的添加/修改。