在 Blazor 中使用 Web Api 创建复杂对象

Creating a complex object using Web Api in Blazor

我正在尝试在我的网站 api 中使用 Post 方法创建一个复杂对象。但是我很难做到这一点,因为当我创建一个 Board 对象时,我要求它有一个 Board.Company.Name 将它与公司相关联。但是,当我 select 一个已经存在的公司名称并处理有效提交时,将使用我选择的 Board.Company.Name 创建一个新公司。然后我显示我创建的董事会,看起来实际上没有公司与之关联。下面我包含了相关代码。这是我使用 C# 和 Blazor 的第一个项目,所以如果我遗漏了任何重要的内容,请告诉我,我会添加它。

公司模式
public class Company
    {
        [Key]
        public Guid Id { get; set; }

        [Required]
        public string Name { get; set; }

        public DateTime Founded { get; set; }
    }
棋盘模型
public class Board
    {
        [Key]
        public Guid Id { get; set; }

        [ValidateComplexType]
        public Company Company { get; set; } = new();

        [Required]
        public string Name { get; set; }

        public string Description { get; set; }

        public List<Ticket> Tickets { get; set; }

        public Board()
        {
        }
    }
Api POST 方法
[HttpPost]
public async Task<ActionResult<Board>> PostBoard(Board board)
{
    _context.Boards.Add(board);
    await _context.SaveChangesAsync();

    return CreatedAtAction("GetBoard", new { id = board.Id }, board);
}
Create_Board
@page "/create_board"

@inject NavigationManager Navigation
@inject HttpClient Http

<div>
    <button class="btn btn-outline-secondary oi oi-arrow-left" @onclick="GoToHome"></button>
    <h3 class="text-center">Create a board</h3>
</div>

<hr />

<EditForm Model="Board" OnValidSubmit="@HandleValidSubmit">
    <ObjectGraphDataAnnotationsValidator />

    <div class="form-group row">
        <label for="Company" class="col-sm-2 col-form-label">Company</label>
        <div class="col-sm-10">
            <InputSelect id="Company" class="form-control" @bind-Value="Board.Company.Name">
                <option value="" disabled selected>Company</option>
                @foreach (var company in Companies)
                {
                    <option>@company.Value.Name</option>
                }
            </InputSelect>
            <ValidationMessage For="@(() => Board.Company.Name)" />
        </div>
    </div>

    <div class="form-group row">
        <label for="Name" class="col-sm-2 col-form-label">Name</label>
        <div class="col-sm-10">
            <InputText id="Name" class="form-control" placeholder="Name" @bind-Value="Board.Name" />
            <ValidationMessage For="@(() => Board.Name)" />
        </div>
    </div>

    <div class="form-group row">
        <label for="Description" class="col-sm-2 col-form-label">Description</label>
        <div class="col-sm-10">
            <InputText id="Description" class="form-control" placeholder="Description" @bind-Value="Board.Description" />
            <ValidationMessage For="@(() => Board.Description)" />
        </div>
    </div>

    <button type="submit" class="btn btn-primary">Submit</button>
</EditForm>

@code {
    private void GoToHome()
    {
        Navigation.NavigateTo("/");
    }

    private Board Board { get; set; } = new Board();

    private Dictionary<Guid, Company> Companies = new Dictionary<Guid, Company>();

    protected override async Task OnInitializedAsync()
    {
        try
        {
            Companies = await Http.GetFromJsonAsync<Dictionary<Guid, Company>>("api/Companies");
        }
        catch (Exception)
        {
            Console.WriteLine("Exception occurred for GET companies");
        }
    }

    private async void HandleValidSubmit()
    {
        try
        {
            var response = await Http.PostAsJsonAsync("/api/Boards", Board);
            response.EnsureSuccessStatusCode();

            var content = await response.Content.ReadAsStringAsync();
            var board = JsonConvert.DeserializeObject<Board>(content);
            Navigation.NavigateTo($"/read_board/{board.Id}");
        }
        catch (Exception)
        {

            Console.WriteLine("Exception occurred for POST board");
        }
    }
}

我认为设置名称不会给您带来任何乐趣;即使上下文存在的时间足够长(它不应该存在;上下文应该只在 API 的请求存在时存在)看到你使用它之前下载的公司名称它会看到一个 Guid.Empty(默认值)和(大概你已经告诉 EF 它是数据库生成的)这将使上下文认为公司是名称为 X

的新公司

相反,我认为我应该让实体遵循典型的“让 CompanyId 成为董事会成员并将其设置在那里”的路线,而不是在新的相关实体上设置名称:

<InputSelect id="Company" class="form-control" @bind-Value="Board.CompanyId">
    <option value="" disabled selected>Company</option>
    @foreach (var company in Companies)
    {
        <option value="@company.Key">@company.Value.Name</option>
    }
</InputSelect>

这应该保存,EF 将看到公司 ID 并连接相关公司。

如果您不同意这种做法(将 CompanyId 实体添加到 Board),您可以采用:

  • 在保存之前通过 ID 下载该公司,并将其指定为公司 - 然后您将使用更改跟踪器之前看到的公司实例,它将知道如何连接到现有公司而不是创建一个新的,例如
<InputSelect id="Company" class="form-control" @bind-Value="Board.Company.Id">
    <option value="" disabled selected>Company</option>
    @foreach (var company in Companies)
    {
        <option value="@company.Key">@company.Value.Name</option>
    }
</InputSelect>

[HttpPost]
public async Task<ActionResult<Board>> PostBoard(Board board)
{
    board.Company = _context.Companies.Find(board.Company.Id); // download existing co with that ID
    _context.Boards.Add(board);
    await _context.SaveChangesAsync();

    return CreatedAtAction("GetBoard", new { id = board.Id }, board);
}

  • 看看欺骗更改 tracker/context 以为它已经看到你用 Id X 创建的新公司。我个人不是粉丝,但是:
[HttpPost]
public async Task<ActionResult<Board>> PostBoard(Board board)
{
    _context.Boards.Add(board);
    _context.Entries(board.Company).State = EntityState.Unchanged; //don't try to save the Company

    await _context.SaveChangesAsync();

    return CreatedAtAction("GetBoard", new { id = board.Id }, board);
}