在父模型/属性中更新子模型/属性的最佳/最有效方法是什么?

时间:2018-05-30 14:58:46

标签: c# asp.net-mvc asp.net-core .net-core asp.net-core-mvc

所以说我ClassA有属性:

//ClassA
int ID
string Name
ClassB SubModel

我还有ClassB具有属性:

//ClassB
int ID
string Name

现在我有了一个视图,用户可以修改ClassA以及ClassA相关的ClassB属性。

修改ClassB的最佳方式是什么?

现在,当我检索ClassA时,我会.Include检索其ClassB,当我向用户公开属性时,我会ClassA.SubModel.Name

这是正确的方法吗?或者我应该单独抓取ClassAClassB,并将它们作为两个单独的属性,当我更新时,调用两个更新来单独更新它们?

2 个答案:

答案 0 :(得分:1)

" best"这样做的方式非常主观,但实现这一目的的一种常用方法是在检索视图所需的数据时使用.Include(正如您上面提到的那样),然后在视图中可以使用Html帮助器和类似下面的表单发布将编辑后的数据发布回控制器:

@model MyApplication.ViewModels.ClassA

@using (Html.BeginForm("Edit", "MyController", FormMethod.Post, new { @class = "form-horizontal", role = "form"))
{
    @Html.EditorFor(x => x.Name)
    @Html.EditorFor(x => x.SubModel.Name)

    <input type="submit" value="Save" />
}

HTML帮助程序将帮助您正确格式化属性的html名称属性,以便从视图到控制器正确地序列化数据。

答案 1 :(得分:0)

我认为没有一个&#34;最好的&#34;实现这一目标的方法。这一切都取决于您的业务。因此,我只想分享我将如何做到这一点。

我的DDD方法

在我的Domain-Drive Design实践项目中,我有两组模型,不包括视图的视图模型:

  • 域模型 - 代表您的业务逻辑,而不是依赖于您的基础架构代码
  • 持久性模型 - 表示如何在您选择的数据库中映射域模型

持久性模型

我可以将ClassAClassB定义为具有一对多关系的实体。

我们假设我使用Entity Framework Core作为ORM,使用SQL Server作为数据库。

public class ClassAEntity
{
    public int Id { get; set; }
    public string Name { get; set; }

    public int ClassBId { get; set; }
    public ClassBEntity ClassB { get; set; }
}

public class ClassBEntity
{
    public int Id { get; set; }
    public string Name { get; set; }

    public List<ClassAEntity> ClassAs { get; set; }
}

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options): base(options) { }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        // Configure relationships
        builder.Entity<ClassAEntity>(b =>
        {
            b.HasKey(x => x.Id);
            b.Property(x => x.Name).IsRequired();

            b.HasOne(x => x.ClassB)
                .WithMany(y => y.ClassAs)
                .HasForeignKey(x => x.ClassBId);

            b.ToTable("ClassA");
        });

        builder.Entity<ClassBEntity>(b =>
        {
            b.HasKey(x => x.Id);
            b.Property(x => x.Name).IsRequired();

            b.ToTable("ClassB");
        });
    }

    public DbSet<ClassAEntity> ClassAs { get; set; }
    public DbSet<ClassBEntity> ClassBs { get; set; }
}

域模型

您可以构建完全不同于构建持久性模型的域模型。事实上,在我的项目中,ClassAClassB位于两个单独的命名空间中,它们都是Aggregate Root - 它们不允许直接相互引用。他们只能通过他们的身份互相引用。你还看到那些私人制定者吗?

public class ClassA : AggregateRoot
{
    public int Id { get; private set; }
    public string Name { get; private set; }

    public int ClassBId { get; private set; }

    private ClassA() { }

    private ClassA(CreateClassACommand command) : base(command.Id)
    {
        this.Name = command.Name;
        this.ClassBId = command.ClassBId;

        // You can create events and store them later on as auditing or
        // have others subscribe this event. 
        AddEvent(new ClassACreated
        {
            // ...
        });
    }

    public static ClassA CreateNew(CreateClassACommand command,
        IValidator<CreateClassACommand> validator)
    {
        // You can have validations here too, with help of FluentValidation
        // library
        validator.ValidateAndThrow(command);

        return new ClassA(command);
    }

    public void UpdateDetails(UpdateClassADetailsCommand command,
        IValidator<UpdateClassADetailsCommand> validator)
    {
        validator.ValidateAndThrow(command);

        this.Name = command.Name;
        this.ClassBId = command.SelectedClassBId;

        AddEvent(new ClassADetailsUpdated 
        {
            // ...
        });
    }
}

希望您看到使用类来表示业务逻辑的好处。您可以在那里安装私有设置器,以防止其他类更改数据。您可以定义对其他具有验证的方法开放的方法。

编辑屏幕

由于我的ClassAClassB是两个单独的聚合,因此我没有计划让屏幕同时更新它们。相反,我有2个独立的控制器来代表它们。

在编辑ClassA屏幕时,我可以提供可用ClassBs列表作为下拉列表,因为他们的关系是一对多的。

同样,没有正确的方法。这一切都取决于您的业务逻辑。

public ClassAController : AdminControllerBase
{
    private readonly AppDbContext _dbContext;

    public ClassAController(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IActionResult Edit(int id)
    {
        // Find the entity by id in the database
        var classAEntity = _dbContext.ClassAs
            .AsNoTracking()
            .SingleOrDefault(x => x.Id == id);
        if (classAEntity == null)
        {
            return NotFound();
        }

        // Find a list of available class Bs
        var availableClassBs = _dbContext.ClassBs
            .AsNoTracking()
            .Where(x => ... your filter ...)
            .OrderBy(x => x.Name)
            .ToDictionary(x => x.Id, x => x.Name);

        // Construct the view model for editing
        var vm = new EditClassAViewModel
        {
            ClassAId = classAEntity.Id,
            Name = classAEntity.Name,
            SelectedClassBId = classAEntity.ClassBId,
            AvailableClassBs = availableClassBs
        };

        return View(vm);
    }
}

以下是编辑屏幕的视图模型。取决于您想要编辑的内容,您可以为此目的构造属性。如果您启用了客户端验证,这也是放置数据注释的正确位置。

public class EditClassAViewModel
{
    [Required]
    public int ClassAId { get; set; }

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

    [Display(Name = "Class b")]
    public int SelectedClassBId { get; set; }

    public IDictionary<int, string> AvailableClassBs { get; set; }
}

编辑ClassA视图

我认为你在这里得到了这个想法,所以我不会发布任何视图的例子,以节省一些空间。

提交

在回发后,控制器捕获视图模型,然后您可以将其转换为您的域模型,从域模型调用正确的操作,它将自己处理验证,然后在最后,您转换您的域模型到您的持久性模型并将其保存回数据库。

注意:在我的项目中,我使用了MediatR库 - 发送和处理请求/命令,AutoMapper库 - 来回转换模型,以及存储库模式,但在这里我只是将所有内容放在一起以简化过程。

[HttpPost]
public IActionResult Edit(EditClassAViewModel model)
{
    var response = new JsonResponse();
    if (!ModelState.IsValid)
    {
        response.AddModalStateErrors(ModelState);
        return Json(response);
    }

    // Get the ClassA entity from the database and convert the persistence
    // model to your domain model. You could have your repository to do
    // both in one step.
    var classAEntity = _dbContext.ClassAs
        .AsNoTracking()
        .SingleOrDefault(x => x.Id == model.ClassAId);
    if (classAEntity == null)
    {
        response.AddError(...);
        return Json(response);
    }

    // Convert the persistence model to domain model. You could use
    // AutoMapper to do so.
    var classA = new ClassA(...);

    // Class the ClassA domain model UpdateDetails method
    classA.UpdateDetails(...);

    // Convert the domain model back to persistence model
    // and save it to the database. You could have your repository to do
    // both in one step.
    var classAPersistenceModel = ...;

    // Since this persistence model is not tracked by EFCore,
    // you need to fetch the entity again from database by Id and update
    // that entity instead.
    // Again, you could have your repository to do that in one step too.
    classAEntity = _dbContext.ClassAs.Find(classAPersistenceModel.Id);
    if (classAEntity != null)
    {
        _dbContext.Entry(classAEntity).CurrentValues
            .SetValues(classAPersistenceModel);
        _dbContext.SaveChanges();
    }
}

免责声明:是的,我知道在这篇文章中我可能会忽略许多新内容,即使用IMediatR的请求/命令模式,使用FluentValidation,AutoMapper配置和存储库模式进行验证。但这篇文章的重点只是为了让您了解我的DDD方法。 XD