更新已拥有的实体 EF Core 5

4

我正在使用.NET 5实现一个API。我有一个名为"学生类"的类,其中有一个属性是地址类型(根据DDD是值对象)。

public class Student 
{
        public long Id{ get; private set; }
        public string FirstName { get; private set; }
        public string LastName { get; private set; }
        public Address Address { get; private set; }
}

public class Address
{
    public string City { get; private set; }
    public string Road { get; private set; }
}

我正在使用 Fluent API 配置 EF Core 5 中的数据库。

 class StudentConfiguration : IEntityTypeConfiguration<Student>
    {

        public void Configure(EntityTypeBuilder<Student> builder)
        {
            builder.ToTable("Students");
            builder.HasKey(x => x.Id);

            builder.Property(x => x.Id).ValueGeneratedNever().IsRequired();
            builder.Property(x => x.FirstName).HasMaxLength(25).IsRequired();
            builder.Property(x => x.LastName).HasMaxLength(50).IsRequired();           


            builder.OwnsOne(x => x.Address, x =>
            {
                x.Property(pp => pp.City)
                    .IsRequired()
                    .HasColumnName("City")
                    .HasMaxLength(20);
                x.Property(pp => pp.Road)
                    .IsRequired()
                    .HasColumnName("Road")
                    .HasMaxLength(40);
            });

        }
    }

因此,我有一个包含ID、名字、姓氏、城市和地址的表。

现在我正在尝试仅更新城市和地址(例如,学生更换住所),但我遇到了不同的异常情况,我不知道如何仅更新这两个列。

public async Task UpdateAddress(Student student)
{
//Firts try 
            //var studentEntry = context.Entry(student);
            //studentEntry.Property(x => x.Address.City).IsModified = true;
            //studentEntry.Property(x => x.Address.Road).IsModified = true;
            //**Exception** 'The expression 'x => x.Address.City' is not a valid member access expression. The expression should represent a simple property or field access: 't => t.MyProperty'. (Parameter 'memberAccessExpression')'

//Second try 
             //var studentEntry = context.Entry(student.Address);
            //studentEntry.Property(x => x.City).IsModified = true;
            //studentEntry.Property(x => x.Road).IsModified = true;
            //**Exception** 'Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. 

//the only method that works is Update but this update the whole object
     context.Update(student);
     await context.SaveChangesAsync();
}

EDIT

public class StudentDbContext : DbContext
    {
        public DbSet<Student> Students { get; set; }

        public StudentDbContext(DbContextOptions<StudentDbContext> options) : base(options)
        {
            ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
            base.OnModelCreating(modelBuilder);
        }
    }

如何仅更新拥有实体的这两个属性?

你能展示一下完整的DbContext吗? - Serge
@Sergey 编辑并添加 dbcontext - user9989070
问题是我使用领域驱动设计方法设置了私有集,只有在我的地址领域中才能更改这些值,而不是在外部。因此,当我尝试设置这些值时,会出现错误。 - user9989070
2个回答

7

Address是一个拥有实体类型,因此按照EF Core的术语,Student.Address属性不是一个属性,而是一个引用导航属性,因此应该通过Reference方法来访问它,而不是通过Property方法(两种方法都不支持属性路径)。然后,您可以使用返回的跟踪条目来访问其成员。

要强制更新Student.Address的某些属性,首先要附加Student实体实例(告诉EF它已经存在)。

var studentEntry = context.Attach(student);

然后使用类似以下的东西

var addressEntry = studentEntry.Reference(e => e.Address);
addressEntry.TargetEntry.Property(e => e.City).IsModified = true;
addressEntry.TargetEntry.Property(e => e.Road).IsModified = true;


1
如何更新整个引用?如果数据库中的所有字段都为NULL,则无法保存所拥有的属性。 - erikscandola
@erikscandola 你的 EF Core 版本是什么?最近有一些关于所谓的可选/必需依赖项的更改,特别是对于拥有类型。 - Ivan Stoev
EF Core在当前版本(EF Core 6+)中可以处理这个问题。在进行更改之前,您需要跟踪实体 - 然后更改跟踪器可以检测到更改并相应地更新它们。 - Puschie

-1

由于您在DBContext配置中设置了EntityFramework不跟踪您的查询,因此您可以尝试以下流程:

首先通过ID从数据库获取学生,然后直接更改您想要更改的那两个属性。

public async Task UpdateAddress(Student student)
{
    // Get existing student by its ID from the database
    var existingStudent = await context.Students
        .Include(x => x.Address)
        .SingleOrDefaultAsync(x => x.Id == student.Id);

    // To prevent null reference exception
    if (existingStudent is null)
        return; // Exception maybe? Depends on your app flow

    // Edit address value object with the method available in your entity, since you're using DDD approach
    existingStudent.ChangeAddress(student.Address);

    // Since your query are not tracked you need to explicity tell EF that this entry is being modified
    context.Entry(existingStudent).State = EntityState.Modified;

    // EF will save only two properties in that case
    await context.SaveChangesAsync();
}

在你的Student实体类中添加以下方法,以提供更改地址的能力:
public void ChangeAddress(Address newAddress)
{
    // Some additional conditions to check the newAddress for nulls, valid values, etc.

    Address = newAddress;
}

你可以将你的地址视为值对象,并用新的地址值对象替换它。


这是先前答案的副本。 - Serge
哦,我在创建这个的时候真的没有注意到。我逐步解释了整个流程,以更好地理解代码的概念和它的来源思路。 - XardasLord
很抱歉,但我认为帖子的所有者有足够的经验来理解我的代码,不需要任何额外的解释。 - Serge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接