I can't ever seem to get my bridge table to update when I leave Entity Framework to automatically manage my mapping.
Classes:
public class Tool
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public List<TargetType> TargetTypes { get; } = [];
}
public class TargetType
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public List<Tool> Tools { get; } = [];
}
I when I go to my Edit/Tool page, I want to be able to change the TargetTypes
a Tool is associated with.
The code seems to work fine when debugging - I load all the target types in the view, the user can select them, and press the save button. However, nothing is added to the bridge table. Entity Framework says it handles all this automatically in this article, so I don't really want to have to create a custom class.
Why does my bridge table not update in my database?
Properties:
[BindProperty]
public Tool Tool { get; set; }
[BindProperty]
public List<Target> Targets { get; set; } = new List<Target>();
[BindProperty]
public List<Guid> SelectedTargetTypes { get; set; } = new List<Guid>();
[BindProperty]
public List<SelectListItem> TargetTypesList { get; set; } = new List<SelectListItem>();
OnPost
method:
public async Task<IActionResult> OnPostAsync()
{
// we want to update the tool options here
if (!ModelState.IsValid)
{
return Page();
}
// Handle the target types
foreach (var type in SelectedTargetTypes)
{
var target = await _context.TargetTypes.FindAsync(type);
if (target != null)
{
Tool.TargetTypes.Add(target);
}
}
_context.Attach(Tool).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ToolExists(Tool.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
.cshtml
for my view:
<div class="container">
<form method="post">
<h3>Tool Settings</h3>
<hr />
<div class="container">
<div class="form-group col-md-6">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Tool.Id" />
<label asp-for="Tool.Name" class="control-label"></label>
<input asp-for="Tool.Name" class="form-control" />
<span asp-validation-for="Tool.Name" class="text-danger"></span>
</div>
<div class="form-group col-md-6">
<label asp-for="Tool.Description" class="control-label"></label>
<input asp-for="Tool.Description" class="form-control" />
<span asp-validation-for="Tool.Description" class="text-danger"></span>
<label asp-for="Tool.ToolType" class="control-label"></label>
<select id="ToolTypeSelect" class="form-control" asp-items="Html.GetEnumSelectList<AttackSurfaceReview.ASRData.Model.ToolType>()"></select>
<span asp-validation-for="Tool.ToolType" class="text-danger"></span>
</div>
<div class="form-group" style="margin-bottom: 10px;">
<div class="col-md-12">
<label asp-for="Tool.ImageUrl" class="control-label"></label>
<input asp-for="Tool.ImageUrl" class="form-control" />
<span asp-validation-for="Tool.ImageUrl" class="text-danger"></span>
</div>
</div>
<label class="control-label">Allowed Targets</label>
@foreach (var targetType in Model.TargetTypesList)
{
<div class="form-check form-switch">
<input name="SelectedTargetTypes" class="form-check-input" type="checkbox" value="@targetType.Value" />
<label class="form-check-label">@targetType.Text</label>
</div>
}
<div class="form-group">
<input style="margin-bottom: 10px;" type="submit" value="Save" class="btn col-md-4 btn-primary" />
</div>
</div>
</form>
</div>
Actually, this is related with how EF core works with relationship entities.
By default, the var target = await _context.TargetTypes.FindAsync(type);
it will not track the relationship entity tool.
If want to also update the tool, you should reference a new entity from the navigation property of an entity that is already tracked by the context, then the entity will be discovered and inserted into the database.
More details, you could refer to below codes:
var target = await _context.TargetTypes.Include(b => b.Tools).FindAsync(type);
if (target != null)
{
Tool.TargetTypes.Add(target);
}
This is as same as putting the _context.Attach(Tool).State = EntityState.Modified;
before adding the TargetTypes.
More details, you could refer to this article and this document.