I have this entity:
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>();
}
I am using this code to update the entity:
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 is a many-to-many relationship. If a developer is added to the project my update method doesn't update the navigation properties. I know that "SetValues" doesn't work on navigation properties. I couldn't find any working solution for my case using .Net6 and EF Core 6. Can anyone help how to update the navigation properties?
Finally after spending days with research I came up with the following solution. Any ideas for improvement are appreciated. They key was to use the tracked entities from the context and not the untracked from the update source:
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;
}