首页 > 解决方案 > 通过表单更改实体参数的正确方法

问题描述

假设我们有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

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

标签: c#entity-frameworkasp.net-core

解决方案


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


推荐阅读