.NET Blazor Server带有EF编辑表单,管理取消并提交

发布于 2025-02-10 01:10:56 字数 2620 浏览 1 评论 0原文

我有很长一段时间以来与Blazor Serds遇到的问题。我有一个EF模型的编辑表格,但我无法弄清楚如何处理取消。这是我的代码:

@inject NavigationManager nav
 
<h3>Edit @_tmpCustomer.FullName</h3>
 
<EditForm Model=@_tmpCustomer>
    <DataAnnotationsValidator/>
    <ValidationSummary/>
 
<label>First Name</label>
<InputText @bind-Value=_tmpCustomer.FName/>
<br/>
 
<label>Last Name</label>
<InputText @bind-Value=_tmpCustomer.LName/>
<br/>
 
<label>Phone Number</label>
<InputText @bind-Value=_tmpCustomer.PhoneNumber/>
<br/>
 
<button class="btn btn-primary" @onclick=UpdateCustomer>Save</button>
<button class="btn btn-primary" @onclick=@(() => NavTo("/customers"))> Cancel</button>
</EditForm>
 
 
@code {
    [Parameter]
    public int customerId { get; set; }
    private Customer _customer { get; set; }
    private Customer _tmpCustomer { get; set; }
 
    protected override async Task OnInitializedAsync()
    {
        await LoadCustomer(customerId);
    }
 
    private async Task LoadCustomer(int customerId)
    {
        _customer = await _customerRepo.GetCustomerFromId(customerId);
        _tmpCustomer = (Customer)_customer.Clone();
    }
 
    private async Task UpdateCustomer()
    {
        _customer = _tmpCustomer;
        await _customerRepo.Update(_customer);
        await NavTo("/customers");
    }
 
    private async Task NavTo(string uri)
    {
        nav.NavigateTo(uri);
    }
}
public class Customer
{
  ...
   public virtual object Clone()
   {
       return this.MemberwiseClone
   }
}

public class CustomerRepo
    {
        private ApplicationDbContext _context;
        public CustomerRepo(ApplicationDbContext context)
        {
            _context = context;
        }
 
        public async Task<List<Customer>> GetAllCustomers()
        {
            return _context.Customers.ToList();
        }
 
        public async Task<Customer> GetCustomerFromId(int customerId)
        {
            return _context.Customers.FirstOrDefault(c => c.Id == customerId);
        }
 
        public async Task Create(Customer customer)
        {
            _context.Add(customer);
            await _context.SaveChangesAsync();
        }
 
        public async Task Update(Customer customer)
        {
            _context.Update(customer);
            await _context.SaveChangesAsync();
        }
    }

问题是我不能同时跟踪同一EF模型的两个实例,我可以分离它,但我认为这不是这样做的干净或正确的方法。

在Blazor Server中取消编辑表格的正确方法是什么?

谢谢 :)

I have a problem that I have had with blazor server side for a long time. I have an edit form for an EF model but I cannot figure out how to handle cancelation. This is my code:

@inject NavigationManager nav
 
<h3>Edit @_tmpCustomer.FullName</h3>
 
<EditForm Model=@_tmpCustomer>
    <DataAnnotationsValidator/>
    <ValidationSummary/>
 
<label>First Name</label>
<InputText @bind-Value=_tmpCustomer.FName/>
<br/>
 
<label>Last Name</label>
<InputText @bind-Value=_tmpCustomer.LName/>
<br/>
 
<label>Phone Number</label>
<InputText @bind-Value=_tmpCustomer.PhoneNumber/>
<br/>
 
<button class="btn btn-primary" @onclick=UpdateCustomer>Save</button>
<button class="btn btn-primary" @onclick=@(() => NavTo("/customers"))> Cancel</button>
</EditForm>
 
 
@code {
    [Parameter]
    public int customerId { get; set; }
    private Customer _customer { get; set; }
    private Customer _tmpCustomer { get; set; }
 
    protected override async Task OnInitializedAsync()
    {
        await LoadCustomer(customerId);
    }
 
    private async Task LoadCustomer(int customerId)
    {
        _customer = await _customerRepo.GetCustomerFromId(customerId);
        _tmpCustomer = (Customer)_customer.Clone();
    }
 
    private async Task UpdateCustomer()
    {
        _customer = _tmpCustomer;
        await _customerRepo.Update(_customer);
        await NavTo("/customers");
    }
 
    private async Task NavTo(string uri)
    {
        nav.NavigateTo(uri);
    }
}
public class Customer
{
  ...
   public virtual object Clone()
   {
       return this.MemberwiseClone
   }
}

public class CustomerRepo
    {
        private ApplicationDbContext _context;
        public CustomerRepo(ApplicationDbContext context)
        {
            _context = context;
        }
 
        public async Task<List<Customer>> GetAllCustomers()
        {
            return _context.Customers.ToList();
        }
 
        public async Task<Customer> GetCustomerFromId(int customerId)
        {
            return _context.Customers.FirstOrDefault(c => c.Id == customerId);
        }
 
        public async Task Create(Customer customer)
        {
            _context.Add(customer);
            await _context.SaveChangesAsync();
        }
 
        public async Task Update(Customer customer)
        {
            _context.Update(customer);
            await _context.SaveChangesAsync();
        }
    }

The problem is that I cannot have 2 instances of the same EF model tracked at the same time, I could detach it but I don't think that's the clean or right way to do this.

What would be the correct way to cancel an edit form in blazor server?

Thanks :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

我乃一代侩神 2025-02-17 01:10:56

问题在于整个用户会话都使用相同的DBContext。这在数据库访问 Blazor Server和EF Core 文档。您必须自己管理DBContext的一生。

文档示例显示了注册DBContextFactory的注册:

builder.Services.AddDbContextFactory<ContactContext>(opt =>
    opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"))

这类似于您在AddDbContext中使用的内容。

然后,该页面注入dbContextFactory而不是实际的dbcontext,并在onInitialized中创建一个新的dbContext实例

@implements IDisposable
@inject IDbContextFactory<ContactContext> DbFactory

.....

protected override async Task OnInitializedAsync()
{
    Busy = true;

    try
    {
        Context = DbFactory.CreateDbContext();

        if (Context is not null && Context.Contacts is not null)
        {
            var contact = await Context.Contacts.SingleOrDefaultAsync(c => c.Id == ContactId);

            if (contact is not null)
            {
                Contact = contact;
            }
        }
    }
    finally
    {
        Busy = false;
    }

    await base.OnInitializedAsync();
}

该页面必须实现idisposable,因此它可以派遣上下文实例远离页面时:

public void Dispose()
{
    Context?.Dispose();
}

说明

在Web应用程序中,依赖项注入范围是控制器操作,这意味着当完成新操作并在完成时执行并处理新操作时,创建了范围范围的服务。这与DBContext的“工作单元”语义很好,因为更改仅用于单个动作。 “取消”是指根本不调用savechanges

Blazor Server的行为就像桌面应用程序一样,范围是整个用户电路(将其视为用户会话)。您必须为每个工作单元创建一个新的DBContext。

做到这一点的一种方法是显式创建一个新的dbcontext,即new ApplicationDbContext()。这有效,但需要对配置进行硬编码。

另一个选项是将DBContextFactory而不是applicationdbcontext注入custome> customerrepo或页面,并根据需要创建DBContext实例。请注意,customerrepo本身现在已经为整个用户会话而活,因此您不能仅仅用DBContext的包装器来依靠它。

The problem is that the same DbContext is used for the entire user session. This is explained in the Database Access section of the Blazor Server and EF Core docs. You'll have to manage the DbContext's lifetime yourself.

The documentation example shows registering a DbContextFactory with :

builder.Services.AddDbContextFactory<ContactContext>(opt =>
    opt.UseSqlite(
quot;Data Source={nameof(ContactContext.ContactsDb)}.db"))

Which is similar to what you'd use with AddDbContext.

Then the page injects the DbContextFactory instead of the actual DbContext and creates a new DbContext instance in OnInitialized

@implements IDisposable
@inject IDbContextFactory<ContactContext> DbFactory

.....

protected override async Task OnInitializedAsync()
{
    Busy = true;

    try
    {
        Context = DbFactory.CreateDbContext();

        if (Context is not null && Context.Contacts is not null)
        {
            var contact = await Context.Contacts.SingleOrDefaultAsync(c => c.Id == ContactId);

            if (contact is not null)
            {
                Contact = contact;
            }
        }
    }
    finally
    {
        Busy = false;
    }

    await base.OnInitializedAsync();
}

The page has to implement IDisposable so it can dispoce the Context instance when you navigate away from the page:

public void Dispose()
{
    Context?.Dispose();
}

Explanation

In web applications the Dependency Injection scope is the controller action, which means scoped services are created when a new action is executed and disposed when it completes. This works nicely with DbContext's Unit-of-Work semantics, as changes are cached only for a single action. "Cancelling" means simply not calling SaveChanges.

Blazor Server behaves like a desktop application though, and the scope is the entire user circuit (think of it as a user session). You'll have to create a new DbContext for each unit of work.

One way to do this is to just create a new DbContext explicitly, ie new ApplicationDbContext(). This works but requires hard-coding the configuration.

Another option is to inject a DbContextFactory instead of the ApplicationDbContext into your CustomerRepo or page, and create the DbContext instance as needed. Note that CustomerRepo itself is now alive for the entire user session, so you can't depend on it working as just a wrapper over DbContext.

夏九 2025-02-17 01:10:56

在Blazor Server中取消编辑表格的正确方法是什么?

我做以下操作:

  1. 所有数据类都是不可变的记录。你不能改变它们。
  2. 我为数据类创建一个编辑类,并从记录中读取数据。
  3. 该课程进行验证和状态管理。例如,您可以随时从编辑类创建记录,并对原件进行平等检查,以查看您的记录状态是否肮脏。
  4. 我创建一个从编辑类创建新记录以提交给EF以更新数据库。
  5. 我使用DBContext工厂并应用“工作单位”原则。

取消编辑很简单,您无法保存!

首先更改为使用dbContextFactory。 MSSQL看起来像这样:

builder.Services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer("ConnectionString"), ServiceLifetime.Singleton);

并更新您的客户存储库以使用此功能。

public class CustomerRepo
{
    private IDbContextFactory<ApplicationDbContext> _dbContextFactory;

    public CustomerRepo(IDbContextFactory<ApplicationDbContext> factory)
        => _dbContextFactory = factory;

创建方法现在变为:

public async ValueTask Create(Customer customer)
{
     using var context = _dbContextFactory.CreateDbContext();
    _context.Add(customer);
    // can check you get 1 back
    await _context.SaveChangesAsync();
}

将客户更改为记录

public record Customer
{
    public int Id { get; init; }
    public string? Name { get; init; }
}

为客户创建编辑类

public class DeoCustomer
{
    private Customer _customer;
    public int Id { get; set; }
    public string? Name { get; set; }

    public DeoCustomer(Customer customer)
    {
        this.Id = customer.Id;
        this.Name = customer.Name;
        _customer = customer;
    }

    public Customer Record =>
        new Customer
        {
            Id = this.Id,
            Name = this.Name
        };

    public bool IsDirty =>
        _customer != this.Record;
}

,然后您的编辑表格可以看起来像这样:

// EditForm using editRecord

@code {
    private DeoCustomer? editRecord = default!;

    private async Task LoadCustomer(int customerId)
    {
        var customer = await _customerRepo.GetCustomerFromId(customerId);
        editRecord = new DeoCustomer(customer);
    }

    private async Task UpdateCustomer()
    {
        if (editRecord.IsDirty)
        {
            var customer = editRecord.Record;
            await _customerRepo.Update(customer);
        }
        await NavTo("/customers");
    }
}

我实际上没有运行代码,因此可能会有一些错别字呢

What would be the correct way to cancel an edit form in blazor server?

I do the following:

  1. All data classes are immutable records. You can't change them.
  2. I create an edit class for the data class and read in the data from the record.
  3. This class does validation and state management. For instance you can create a record from the edit class at any time and do equality check against the original to see if your record state is dirty.
  4. I create a new record from the edit class to submit to EF to update the database.
  5. I use the DBContext factory and apply "Unit of Work" principles.

Canceling the edit is then simple, you don't save!

First change over to using the DBContextFactory. The MSSQL one looks like this:

builder.Services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer("ConnectionString"), ServiceLifetime.Singleton);

And update your Customer Repo to use this.

public class CustomerRepo
{
    private IDbContextFactory<ApplicationDbContext> _dbContextFactory;

    public CustomerRepo(IDbContextFactory<ApplicationDbContext> factory)
        => _dbContextFactory = factory;

And the Create method now becomes:

public async ValueTask Create(Customer customer)
{
     using var context = _dbContextFactory.CreateDbContext();
    _context.Add(customer);
    // can check you get 1 back
    await _context.SaveChangesAsync();
}

Change Customer to a record

public record Customer
{
    public int Id { get; init; }
    public string? Name { get; init; }
}

Create an edit class for Customer

public class DeoCustomer
{
    private Customer _customer;
    public int Id { get; set; }
    public string? Name { get; set; }

    public DeoCustomer(Customer customer)
    {
        this.Id = customer.Id;
        this.Name = customer.Name;
        _customer = customer;
    }

    public Customer Record =>
        new Customer
        {
            Id = this.Id,
            Name = this.Name
        };

    public bool IsDirty =>
        _customer != this.Record;
}

And then your edit form can look something like this:

// EditForm using editRecord

@code {
    private DeoCustomer? editRecord = default!;

    private async Task LoadCustomer(int customerId)
    {
        var customer = await _customerRepo.GetCustomerFromId(customerId);
        editRecord = new DeoCustomer(customer);
    }

    private async Task UpdateCustomer()
    {
        if (editRecord.IsDirty)
        {
            var customer = editRecord.Record;
            await _customerRepo.Update(customer);
        }
        await NavTo("/customers");
    }
}

I haven't actually run the code so there may be some typos!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文