所以说我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
,并将它们作为两个单独的属性,当我更新时,调用两个更新来单独更新它们?
答案 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;实现这一目标的方法。这一切都取决于您的业务。因此,我只想分享我将如何做到这一点。
在我的Domain-Drive Design实践项目中,我有两组模型,不包括视图的视图模型:
我可以将ClassA
和ClassB
定义为具有一对多关系的实体。
我们假设我使用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; }
}
您可以构建完全不同于构建持久性模型的域模型。事实上,在我的项目中,ClassA
和ClassB
位于两个单独的命名空间中,它们都是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
{
// ...
});
}
}
希望您看到使用类来表示业务逻辑的好处。您可以在那里安装私有设置器,以防止其他类更改数据。您可以定义对其他具有验证的方法开放的方法。
由于我的ClassA
和ClassB
是两个单独的聚合,因此我没有计划让屏幕同时更新它们。相反,我有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; }
}
我认为你在这里得到了这个想法,所以我不会发布任何视图的例子,以节省一些空间。
在回发后,控制器捕获视图模型,然后您可以将其转换为您的域模型,从域模型调用正确的操作,它将自己处理验证,然后在最后,您转换您的域模型到您的持久性模型并将其保存回数据库。
注意:在我的项目中,我使用了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