【问题标题】:Proper way to change entity parameters through form通过表单更改实体参数的正确方法
【发布时间】:2019-09-13 13:02:03
【问题描述】:

假设我们有 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-to-Group 具有多对多关系,User-to-Referal有一对多的关系。他们都对 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

所以我想知道是否有比手动更新字段更好的方法来更新实体?

【问题讨论】:

    标签: c# entity-framework asp.net-core


    【解决方案1】:

    您可以使用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/

    【讨论】:

      猜你喜欢
      • 2019-09-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-09-23
      相关资源
      最近更新 更多