c# - 通过表单更改实体参数的正确方法
问题描述
假设我们有User实体:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string StatusMessage{ get; set; }
public string PasswordHash { get; set; }
public Group Group { get; set; }
public Referal Referal { get; set; }
}
其中,用户对组具有多对多关系,而用户对推荐人具有一对多关系。他们都对User有外键约束。
我作为用户突然意识到我目前的状态并不能完全代表我的个性 - 所以我决定改变它。我们有简单的表格来提交新的状态。
@model User
<form asp-antiforgery="true" method="post" role="form">
<div class="validation" asp-validation-summary="ModelOnly"></div>
<div class="form-group">
<div>
<input hidden asp-for="Id" type="number" name="Id" value="@Model.Id">
</div>
</div>
<div class="form-group">
<label for="Status">Status:</label>
<div>
<input required asp-for="Status" type="text" name="Status">
</div>
</div>
</form>
和两个动作来处理它。
[HttpGet("account/edit")]
public IActionResult Edit(int accountId, string Status)
{
User user = db.Users.SingleOrDefault(p => p.Id == accountId);
return View(user);
}
[HttpPost("account/edit")]
public IActionResult Edit(int Id, string Status)
{
User user = db.Users.SingleOrDefault(p => p.Id == accountId);
user.StatusMessage = Status;
db.SaveChanges()
return RedirectToAction("MainView");
}
没看到什么奇怪的吗?在 post 方法中,我们再次连接到我们的数据库以获取正确的实体,因此它所拥有的任何关系都将被保存。如果我们只有几个字段要更新,这是一个好方法。但是如果我们有 10-15 个或更多字段,它会变得越来越难看。
[HttpPost("account/edit")]
public IActionResult Edit(int Id, string Status, string FavouriteGirl, string LastWatchedTVShow, int NumberOfFingers, DateTime YearOfFirstKiss, string DogName)
{
User user = db.Users.SingleOrDefault(p => p.Id == accountId);
user.StatusMessage = Status;
user.FavouriteGirl = FavouriteGirl;
user.LastWatchedTVShow = LastWatchedTVShow;
user.NumberOfFingers= NumberOfFingers;
user.YearOfFirstKiss= YearOfFirstKiss;
user.DogName = DogName;
db.SaveChanges()
return RedirectToAction("MainView");
}
这在某种程度上很方便,我们明确声明哪些字段将被更改,但它们变成了硬编码,我们可以在视图中查看更改。
处理它的一个好方法是传递User对象。
[HttpPost("account/edit")]
public IActionResult Edit(User user)
但实际上我找不到任何将它绑定到实体的好方法。
[HttpPost("account/edit")]
public IActionResult Edit(User user)
{
//None of this will work
User userFromDb = db.Users.SingleOrDefault(p => p.Id == user.Id);
userFromDb = user;
db.SaveChanges();
//we just changed reference to variable, no effect on entity
user.Group = userFromDb.Group;
user.Referal = userFromDb.Referal;
db.SaveChanges();
//will cause FK constraint errors
}
最后,我们将手动更新字段,但从类userFromDb.StatusMessage = user.StatusMessage
所以我想知道是否有比手动更新它的字段更好的方法来更新实体?
解决方案
您可以使用AutoMapper
来实现更新。对于您的场景,User-to-Group 具有多对多关系,而 User-to-Referal 具有一对多关系,使用连接表来实现多对多关系,您可以像下面这样定义它们:
User
模型,UserGroup
模型,Group
模型,Referal
模型
public class User
{
public int Id { get; set; }
public string Gender { get; set; }
[MaxLength(30)]
public string FirstName { get; set; }
[MaxLength(30)]
public string LastName { get; set; }
[EmailAddress]
public string Email { get; set; }
[Phone]
public string Phone { get; set; }
public string StatusMessage { get; set; }
public List<UserGroups> UserGroups { get; set; }
public List<Referal> Referals { get; set; }
}
public class UserGroups
{
public int UserId { get; set; }
public User User { get; set; }
public int GroupId { get; set; }
public Group Group { get; set; }
}
public class Group
{
public int GroupId { get; set; }
public string GroupName { get; set; }
public List<UserGroups> UserGroups { get; set; }
}
public class Referal
{
public int ReferalId { get; set; }
public string ReferalName { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
UserForUpdateDto
用于映射的模型和GroupViewModel
模型
public class UserForUpdateDto
{
public int UserId { get; set; }
public string Status { get; set; }
public List<Referal> Referals { get; set; }
public List<GroupViewModel> Groups { get; set; }
}
public class GroupViewModel
{
public int GroupId { get; set; }
public string GroupName { get; set; }
}
创建UserProfile
类
public class UserProfile: Profile
{
public UserProfile()
{
CreateMap<UserForUpdateDto, User>()
.ForMember(des=>des.StatusMessage,opt=>opt.MapFrom(src=>src.Status))
.ForMember(des => des.Id, opt => opt.MapFrom(src => src.UserId))
.ForMember(des=>des.Referals,opt=>opt.MapFrom(src=>src.Referals))
.ForMember(des => des.UserGroups, opt => opt.MapFrom(src => src.Groups))
.AfterMap((src,des)=> {
foreach (var group in des.UserGroups)
{
group.UserId = src.UserId;
}
});
CreateMap<GroupViewModel, UserGroups>()
.ForMember(des => des.Group, opt => opt.MapFrom(src => src));
CreateMap<GroupViewModel, Group>();
CreateMap<User, UserForUpdateDto>()
.ForMember(des => des.UserId, opt => opt.MapFrom(src => src.Id))
.ForMember(des => des.Status, opt => opt.MapFrom(src => src.StatusMessage))
.ForMember(des => des.Referals, opt => opt.MapFrom(src => src.Referals))
.ForMember(des => des.Groups, opt => opt.MapFrom(src => src.UserGroups));
CreateMap<UserGroups, GroupViewModel>()
.ForMember(des => des.GroupId, opt => opt.MapFrom(src => src.Group.GroupId))
.ForMember(des => des.GroupName, opt => opt.MapFrom(src => src.Group.GroupName));
//CreateMap<Group, GroupViewModel>();
}
}
控制器
// GET: Users/Edit/5
public async Task<IActionResult> EditTest(int? id)
{
if (id == null)
{
return NotFound();
}
var user =await _context.Users
.Include(u=>u.Referals)
.Include(u=>u.UserGroups).ThenInclude(ug=>ug.Group)
.SingleOrDefaultAsync(u=>u.Id==id);
if (user == null)
{
return NotFound();
}
var userForUpdate = _mapper.Map<UserForUpdateDto>(user);
return View(userForUpdate);
}
// POST: Users/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditTest(int id, UserForUpdateDto userForUpdate)
{
if (id != userForUpdate.UserId)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
var user = _context.Users.AsNoTracking()
.Include(u => u.Referals)
.Include(u=>u.UserGroups)
.ThenInclude(ug => ug.Group).SingleOrDefault(u => u.Id == id);
_mapper.Map(userForUpdate,user);
_context.Users.Update(user);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!UserExists(userForUpdate.UserId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(userForUpdate);
}
关于 AutoMapper with Asp.Net Core,可以参考 https://sensibledev.com/asp-net-core-automapper/
推荐阅读
- javascript - 编辑 - 在 Instagram 上选择图像不适用于我的 chrome 扩展
- python - 返回从集合 X 到集合 Y 的 top-k 最近距离
- if-statement - 如何根据机器人中的 IF/ELSE 条件为变量赋值?
- python - 如何找出我正在使用的 python 虚拟环境?
- android - Microsoft Face API (Android) - 将人员添加到人员组
- java - 有什么理由让 Spring 单例 bean 中的私有方法成为静态的?
- javascript - 在使用 Firebase 的 React-native GiftedChat 聊天应用程序中加载早期消息时遇到问题
- python - 优化和改进模拟投掷棒球的 3D 轨迹的 Python 代码
- haskell - 了解 Haskell 类型类在类型声明中的使用
- python - 在 Python/Micropython 中使用 ADS1x15 库和 ADS1114