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

C# 什么';更新父模型/属性中的子模型/属性的最佳/最有效方法是什么?,c#,asp.net-mvc,asp.net-core,.net-core,asp.net-core-mvc,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等操作

假设我有
ClassA
,它具有以下属性:

//ClassA
int ID
string Name
ClassB SubModel
我还有
ClassB
,它具有以下属性:

//ClassB
int ID
string Name
现在我有了一个视图,用户可以在其中修改
ClassA
以及
ClassA
关联的
ClassB
属性

修改
ClassB
的最佳方式是什么

现在,当我检索
ClassA
时,我执行
.Include
来检索它的
ClassB
,当我向用户公开属性时,我执行
ClassA.SubModel.Name
等操作

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

最佳实现这一点的方法非常主观,但实现这一点的一种常用方法是使用。在检索视图所需的数据时包括(正如您在上面提到的那样),然后在视图中可以使用Html帮助程序和表单post,如下图所示,将编辑后的数据发布回控制器:

@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" />
}
@model MyApplication.ViewModels.ClassA
@使用(Html.BeginForm(“Edit”、“MyController”、FormMethod.Post、new{@class=“form horizontal”、role=“form”))
{
@EditorFor(x=>x.Name)
@EditorFor(x=>x.SubModel.Name)
}

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

我认为没有一种“最佳”方法可以做到这一点。这取决于您的业务。因此,我只想分享我将如何做到这一点

我的DDD方法 在我的Domain Drive Design practice项目中,我有两组模型,不包括视图的视图模型:

  • 域模型-表示您的业务逻辑,而不依赖于您的基础架构代码
  • 持久性模型-表示如何在所选数据库中映射域模型
持久性模型 我可以将
ClassA
ClassB
定义为具有一对多关系的实体

假设我使用
entityframeworkcore
作为ORM,使用
sqlserver
作为数据库

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; }
}
希望您能看到使用类来表示业务逻辑的好处。您可以在其中使用私有setter来防止其他类更改数据。您可以定义对其他类开放的方法,这些方法具有适当的验证

编辑屏幕 由于我的
ClassA
ClassB
是两个单独的聚合,我不打算同时用屏幕来更新它们。相反,我有两个单独的控制器来表示它们

在编辑
ClassA
屏幕上,我可以提供可用
classb
的下拉列表,因为它们是一对多的关系

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

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; }
}

免责声明:是的,我知道我在这篇文章中可能忽略了很多新东西,例如,使用IMeditor的请求/命令模式、使用FluentValidation的验证、AutoMapper配置和存储库模式。但这篇文章的重点只是让你了解我的DDD方法。XD

是什么让你认为只有一个“最佳”呢方法?您可以创建一个新的
ClassC
,它将是该操作/视图的模型,它将是
ClassA
ClassB
的合并。这样,您只需在视图中处理一个平面类,就可以在控制器中转换/映射到您的持久类。查找POCO类。
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; }
}
[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();
    }
}