更新 Entity Framework 多对多导航

Update Entity Framework Many to Many Navigation

我有这个实体:

public class Project : ModelBase
{
    private string _title = Resources.NewProject, _description;

    /// <summary>
    /// <c>Create</c> creates a new project with empty parameter
    /// </summary>
    /// <returns>A project</returns>
    public static Project Create() => new();

    /// <summary>
    /// The projects's title
    /// </summary>
    [JsonPropertyName("title")]
    public string Title { get => _title; set => SetProperty(ref _title, value); }

    /// <summary>
    /// The projects's description
    /// </summary>
    [JsonPropertyName("description")]
    public string Description { get => _description; set => SetProperty(ref _description, value); }

    /// <summary>
    /// The collection of developers assigned to the project
    /// </summary>
    [JsonPropertyName("developers")]
    public ICollection<Developer> Developers { get; set; } = new HashSet<Developer>();

    /// <summary>
    /// The collection of incidents assigned to the project
    /// </summary>
    [JsonPropertyName("incidents")]
    public ICollection<Incident> Incidents { get; set; } = new HashSet<Incident>();
}

我正在使用此代码更新实体:

public async Task<bool> UpdateAsync(IncidentManager.Common.Models.Project project)
    {
        await _semaphore.WaitAsync();
        try
        {
            using var context = new IncidentManagerContext(_connectionString);
            context.Entry(await context.Projects.FirstOrDefaultAsync(x => x.Id == project.Id)).CurrentValues.SetValues(project);
            bool saveFailed;
            do
            {
                saveFailed = false;
                try
                {
                    await context.SaveChangesAsync();
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    saveFailed = true;
                    var entry = ex.Entries.Single();
                    entry.OriginalValues.SetValues(entry.GetDatabaseValues());
                }
            } while (saveFailed);
        }
        catch (Exception) { return false; }
        finally { _semaphore.Release(); }
        return true;
    }

Project to Developer 是多对多的关系。如果将开发人员添加到项目中,我的更新方法不会更新导航属性。我知道“SetValues”不适用于导航属性。我无法使用 .Net6 和 EF Core 6 为我的案例找到任何可行的解决方案。任何人都可以帮助更新导航属性吗?

经过几天的研究,我终于想出了以下解决方案。任何改进的想法表示赞赏。他们的关键是使用来自上下文的跟踪实体,而不是来自更新源的未跟踪实体:

public async Task<bool> UpdateAsync(IncidentManager.Common.Models.Project project)
    {
        await _semaphore.WaitAsync();
        try
        {
            //Create a new context
            using var context = new IncidentManagerContext(_connectionString);
            //Get the existing entry
            var existing = context.Entry(await context.Projects.Where(x => x.Id == project.Id).Include(x => x.Developers).FirstOrDefaultAsync());
            //Update the existing entry's non-navigational properties
            existing.CurrentValues.SetValues(project);
            //Add new relations if any
            foreach (var incomingDeveloper in project.Developers)
            {
                bool add = true;
                foreach (var existingDeveloper in existing.Entity.Developers)
                {
                    if (incomingDeveloper.Id == existingDeveloper.Id)
                    {
                        add = false;
                        break;
                    }
                }
                //Add from context source, not from incoming
                if (add) existing.Entity.Developers.Add(await context.Developers.FindAsync(incomingDeveloper.Id));
            }
            //Remove relations if any
            foreach (var existingDeveloper in existing.Entity.Developers)
            {
                bool remove = true;
                foreach (var incomingDeveloper in project.Developers)
                {
                    if (incomingDeveloper.Id == existingDeveloper.Id)
                    {
                        remove = false;
                        break;
                    }
                }
                //Remove from context source, not from incoming
                if (remove) existing.Entity.Developers.Remove(await context.Developers.FindAsync(existingDeveloper.Id));
            }
            bool saveFailed;
            do
            {
                saveFailed = false;
                try
                {
                    await context.SaveChangesAsync();
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    saveFailed = true;
                    var entry = ex.Entries.Single();
                    entry.OriginalValues.SetValues(entry.GetDatabaseValues());
                }
            } while (saveFailed);
        }
        catch (Exception) { return false; }
        finally { _semaphore.Release(); }
        return true;
    }