需要帮助来更新模型

时间:2018-12-31 11:28:17

标签: .net-core

我有一个名为 ClubApplicationUser 的模型,它是 Club ApplicationUser 之间的桥梁,该模型是 Identity User 的扩展模型。 strong>模型:

public class ClubApplicationUser
{
    public Guid ClubID { get; set; }

    public Club Club { get; set; }

    public string Id { get; set; }

    public ApplicationUser ApplicationUser { get; set; }

    public DateTime DateCreated { get; set; }

    public string CreatedBy { get; set; }

    public DateTime LastDateModified { get; set; }

    public string LastModifiedBy { get; set; }

    public DateTime? DateDeleted { get; set; }

    public string DeletedBy { get; set; }

    public bool IsDeleted { get; set; }

    [Timestamp]
    public byte[] RowVersion { get; set; }

    [ForeignKey("CreatedBy")]
    public ApplicationUser ClubApplicationCreatedUser { get; set; }

    [ForeignKey("LastModifiedBy")]
    public ApplicationUser ClubApplicationLastModifiedUser { get; set; }



}

在ApplicationDBContext-OnModelCreating中,我们定义了关系:

builder.Entity<ClubApplicationUser>()
                .HasKey(bc => new { bc.ClubID, bc.Id });

        builder.Entity<ClubApplicationUser>()
                .HasOne(bc => bc.Club)
                .WithMany(b => b.ClubApplicationUsers)
                .HasForeignKey(bc => bc.ClubID);

        builder.Entity<ClubApplicationUser>()
                .HasOne(bc => bc.ApplicationUser)
                .WithMany(c => c.ClubApplicationUsers)
                .HasForeignKey(bc => bc.Id);

我们有一个问题,无法更新,并且出现错误:

  

InvalidOperationException:实体类型上的属性'ClubID'   'ClubApplicationUser'是密钥的一部分,因此无法进行修改或   标记为已修改。用以下方法更改现有实体的主体   标识外键首先删除依赖项并调用   然后,“ SaveChanges”将依赖项与新的主体相关联。

这是AssignClub.cs:

public class AssignClubUserModel : ClubNamePageModel
{
    private readonly AthlosifyWebArchery.Data.ApplicationDbContext _context;


    public AssignClubUserModel(AthlosifyWebArchery.Data.ApplicationDbContext context)
    {
        _context = context;
    }

    public class AssignClubUserViewModel<ApplicationUser>
    {

        public string FirstName { get; set; }
        public string LastName { get; set; }

        public string UserName { get; set; }

        public Guid SelectedClubID { get; set; }

        public byte[] RowVersion { get; set; }

    }

    [BindProperty]
    public AssignClubUserViewModel<ApplicationUser> AssignClubUser { get; set; }

    public SelectList ClubNameSL { get; set; }

    public async Task<IActionResult> OnGetAsync(Guid? id)
    {



        if (id == null)
            return NotFound();

        var user = await _context.Users
                        .Include(u => u.ClubApplicationUsers)
                        .Where(t => t.Id == id.ToString())
                        .Select(t => new AssignClubUserViewModel<ApplicationUser>
                        {
                            FirstName = t.FirstName,
                            LastName = t.LastName,
                            UserName = t.UserName,
                            SelectedClubID = t.ClubApplicationUsers.ElementAt(0).ClubID,
                            RowVersion =  t.RowVersion
                        }).SingleAsync();


        AssignClubUser = user;



        // Use strongly typed data rather than ViewData.
        ClubNameSL = new SelectList(_context.Club, "ClubID", "Name");

        //PopulateClubsDropDownList(_context);

        return Page();
    }

    public async Task<IActionResult> OnPostAsync(Guid id)
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        // 1st approach: 
        // Modify the bridge model directly

        var clubApplicationUserToUpdate = await _context.ClubApplicationUser
                                                    .FirstOrDefaultAsync(m => m.Id == id.ToString());

        if (clubApplicationUserToUpdate == null) 
        {
            return await HandleDeletedUser();
        }

        _context.Entry(clubApplicationUserToUpdate)
            .Property("RowVersion").OriginalValue = AssignClubUser.RowVersion;

        _context.Entry(clubApplicationUserToUpdate)
            .Property("ClubID").OriginalValue = AssignClubUser.SelectedClubID;

        await _context.SaveChangesAsync();


        // 2nd approach: 
        // Soft -Delete and Add 
        // Did the soft-deleting and managed to add a new one BUT then die the roll back (adding the old one)
        // Result: Violation of PRIMARY KEY constraint 'PK_ClubApplicationUser'. 
        // Cannot insert duplicate key in object 
        // Due to duplicate key

        /*var clubApplicatonUserToRemove = await _context.ClubApplicationUser
                                        .FirstOrDefaultAsync(m => m.Id == id.ToString());
        ClubApplicationUser clubApplicatonUserToAdd = new ClubApplicationUser();
        clubApplicatonUserToAdd.Id = id.ToString();
        clubApplicatonUserToAdd.ClubID = AssignClubUser.SelectedClubID;


        //_context.Entry(clubApplicatonUserToRemove)
        //    .Property("RowVersion").OriginalValue = User.RowVersion;

        if (clubApplicatonUserToRemove != null)
        {
            _context.ClubApplicationUser.Remove(clubApplicatonUserToRemove);
            await _context.SaveChangesAsync();
            _context.ClubApplicationUser.Add(clubApplicatonUserToAdd);
            await _context.SaveChangesAsync();
        }*/




        return Page();
    }

    private async Task<IActionResult> HandleDeletedUser()
    {
        //ClubA deletedClubApplicationUser = new ApplicationUser();
        //ModelState.AddModelError(string.Empty,
        //    "Unable to save. The user was deleted by another user.");
        //ClubNameSL = new SelectList(_context.Roles, "Id", "Name", User.UserRoles.ElementAt(0).RoleId);
        return Page();
    }

    private async Task setDbErrorMessage(ApplicationUser dbValues,
            ApplicationUser clientValues, ApplicationDbContext context)
    {

        if (dbValues.FirstName != clientValues.FirstName)
        {
            ModelState.AddModelError("User.FirstName",
                $"Current value: {dbValues.FirstName}");
        }

        if (dbValues.LastName != clientValues.LastName)
        {
            ModelState.AddModelError("User.LastName",
                $"Current value: {dbValues.LastName}");
        }

        if (dbValues.Email != clientValues.Email)
        {
            ModelState.AddModelError("User.Email",
                $"Current value: {dbValues.Email}");
        }



        ModelState.AddModelError(string.Empty,
            "The record you attempted to edit "
          + "was modified by another user after you. The "
          + "edit operation was canceled and the current values in the database "
          + "have been displayed. If you still want to edit this record, click "
          + "the Save button again.");
    }

}

...和AssignClub.cshtml:

@page
@model AthlosifyWebArchery.Pages.Administrators.Users.AssignClubUserModel
@{
  ViewData["Title"] = "Assign Club";
}
<h2>Assign Club</h2>
<h4>User</h4>
<hr />
<div class="row">
<div class="col-md-4">
    <form method="post">
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <div class="form-group">
            <label asp-for="AssignClubUser.FirstName" class="control-label"> 
</label>
            <input asp-for="AssignClubUser.FirstName" disabled class="form- 
control" />
        </div>
        <div class="form-group">
            <label asp-for="AssignClubUser.LastName" class="control-label"> 
</label>
            <input asp-for="AssignClubUser.LastName" disabled class="form-control" />
        </div>
        <div class="form-group">
            <label asp-for="AssignClubUser.UserName" class="control-label"> 
</label>
            <input asp-for="AssignClubUser.UserName" disabled class="form-control" />
        </div>
        <div class="form-group">
            <label class="control-label">Club</label>
            <select asp-for="AssignClubUser.SelectedClubID" class="form-control"
                    asp-items="@Model.ClubNameSL">
                <option value="">-- Select Club --</option>
            </select>
            <span asp-validation-for="AssignClubUser.SelectedClubID" class="text-danger"></span>
        </div>

        <div class="form-group">
            <input type="submit" value="Save" class="btn btn-default" />
        </div>
    </form>
</div>
</div>
<div>
    <a asp-page="./Index">Back to List</a>
</div>
@section Scripts {
  @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}  

环境: .Net Core 2.2 剃刀页面

更新-1:

如果我们直接通过以下操作在数据库上进行更新:

UPDATE [ClubApplicationUser]
SET ClubID = '85715C34-AFC6-4498-DA7F-08D66CAE7A01'
WHERE Id = 'ecbd27b4-03bc-4b99-82b3-76d9aa5bc7fc'

我们可以更新此问题。因此,它看起来像.Net核心模型中的约束。

4 个答案:

答案 0 :(得分:2)

我认为最好的解决方案是您需要删除并插入而不是更新,尽管实际上给您的ClubApplicationUser可能意味着对IsDeleted字段进行更新,而不是实际执行Delete。

如果您以自己的域的逻辑来考虑,我认为用户通常不会从一个俱乐部的成员变成另一个俱乐部的成员,而是会离开(删除)一个俱乐部并加入(插入)另一个俱乐部。

尽管我可以提出另一个可以进行更新的域,所以我认为这不是一个很好的通用论点。

以下代码显示问题的简化版本。您可以看到测试允许插入和删除,但由于更新失败

public class Club
{
    public int Id { get; set; }
    public string Name { get; set; }

    public IList<ClubUser> Users { get; set; }
}

public class User
{
    public int Id { get; set; }

    public string Name { get; set; }

    public IList<ClubUser> Clubs { get; set; }
}

public class ClubUser
{
    public int ClubID { get; set; }

    public Club Club { get; set; }

    public int Id { get; set; }

    public User User { get; set; }

    public string Extra { get; set; }
}

public class ApplicationDbContext : Microsoft.EntityFrameworkCore.DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<Club> Clubs { get; set; }
    public DbSet<ClubUser> ClubUsers { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=.;Database=Spike;Trusted_Connection=True;");
    }

    protected override void OnModelCreating(Microsoft.EntityFrameworkCore.ModelBuilder builder)
    {
        builder.Entity<Club>()
            .HasKey(c => c.Id );

        builder.Entity<User>()
            .HasKey(c => c.Id );

        builder.Entity<ClubUser>()
            .HasKey(cu => new { cu.ClubID, cu.Id });

        builder.Entity<ClubUser>()
                .HasOne<Club>(cu => cu.Club)
                .WithMany(u => u.Users)
                .HasForeignKey(bc => bc.ClubID);

        builder.Entity<ClubUser>()
                .HasOne<User>(cu => cu.User)
                .WithMany(c => c.Clubs)
                .HasForeignKey(cu => cu.Id);

    }
}

[TestClass]
public class ManyToMany
{
    [TestMethod]
    public void DeleteAndInsert()
    {
        var context = new ApplicationDbContext();

        var clubusers = context.ClubUsers;
        var clubs = context.Clubs;
        var users = context.Users;

        var original = clubusers.First();
        clubusers.Remove(original);

        var newClubUser = new ClubUser
        {
            Club = clubs.Last(),
            User = users.First(),
            Extra = "Another"
        };

        clubusers.Add(newClubUser);

        context.SaveChanges();
    }

    [TestMethod]
    public void Update()
    {
        var context = new ApplicationDbContext();

        var clubusers = context.ClubUsers;
        var clubs = context.Clubs;
        var users = context.Users;

        var update = clubusers.First();
        update.Club = clubs.Last();
        update.Extra = "Changed";

        Assert.ThrowsException<InvalidOperationException>( () =>  context.SaveChanges());
    }
}

要初始化测试数据库:

ALTER TABLE [dbo].[ClubUsers] DROP CONSTRAINT [FK_ClubUser_User]
GO

ALTER TABLE [dbo].[ClubUsers] DROP CONSTRAINT [FK_ClubUser_Club]
GO

DROP TABLE [dbo].[ClubUsers]
GO

DROP TABLE [dbo].[Clubs]
GO

DROP TABLE [dbo].[Users]
GO

CREATE TABLE [dbo].[Clubs](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Name] [varchar](50) NOT NULL,
 CONSTRAINT [PK_Club] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[Users](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Name] [varchar](50) NOT NULL,
 CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[ClubUsers](
    [ClubId] [int] NOT NULL,
    [Id] [int] NOT NULL,
    [Extra] [varchar](50) NOT NULL,
 CONSTRAINT [PK_ClubUser] PRIMARY KEY CLUSTERED 
(
    [ClubId] ASC,
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[ClubUsers]  WITH CHECK ADD  CONSTRAINT [FK_ClubUser_Club] FOREIGN KEY([ClubId])
REFERENCES [dbo].[Clubs] ([Id])
GO

ALTER TABLE [dbo].[ClubUsers] CHECK CONSTRAINT [FK_ClubUser_Club]
GO

ALTER TABLE [dbo].[ClubUsers]  WITH CHECK ADD  CONSTRAINT [FK_ClubUser_User] FOREIGN KEY([Id])
REFERENCES [dbo].[Users] ([Id])
GO

ALTER TABLE [dbo].[ClubUsers] CHECK CONSTRAINT [FK_ClubUser_User]
GO

INSERT Clubs(Name)
VALUES ('GlenEagles');

INSERT Clubs(Name)
VALUES ('StAndrews');

INSERT Clubs(Name)
VALUES ('Wentworth');

INSERT dbo.[Users](Name)
VALUES ('Pete');

INSERT dbo.[Users](Name)
VALUES ('Dave');

INSERT ClubUsers(ClubId, Id, Extra)
VALUES (1,1, 'Hello');

答案 1 :(得分:2)

*无效,请参见评论*

我的第三个选择是最快实现的,但是我不确定所有的含义。

如果将OnModelCreating更改为设置索引而不是键

        builder.Entity<ClubUser>()
            .HasKey(cu => new { cu.ClubID, cu.Id });

成为

        builder.Entity<ClubUser>()
            .HasIndex(cu => new { cu.ClubID, cu.Id });

该更新现在可以正常工作,但是您在ClubUser上没有密钥,这可能会导致其他问题。

答案 2 :(得分:2)

关于:

  

InvalidOperationException:实体类型上的属性'ClubID'   “ ClubApplicationUser”是密钥的一部分...

ClubApplicationUsers表中的

PrimaryKey既是ClubID又是ID。 您不能仅通过ID更改现有记录。

例如:

var clubApplicationUserToUpdate = await _context.ClubApplicationUser
    .FirstOrDefaultAsync(m => m.Id == id.ToString());

必须是这样的:

var clubApplicationUserToUpdate = await _context.ClubApplicationUser
    .FirstOrDefaultAsync(m => m.Id == id.ToString() && m.ClubID == AssignClubUser.SelectedClubID.ToString());

或者:

var clubApplicationUsersToUpdate = await _context.ClubApplicationUser
        .Where(m => m.Id == id.ToString()).ToList();

关于:

  

第二种方法:
  ...
  结果:违反了PRIMARY KEY约束'PK_ClubApplicationUser'。

我将举例说明:

Clubs: 1, 2, 3
ApplicationUsers: A, B, C
ClubApplicationUser: A1, A2

尝试删除A1,然后添加A2-表示A2已经存在。

解决方案更接近第二种方法:

public async Task<IActionResult> OnPostAsync(Guid id)
{
    if (!this.ModelState.IsValid)
    {
        return Page();
    }

    //delete all club memberships and add new one
    var clubApplicatonUsersToRemove = await _context.ClubApplicationUser
        .Where(m => m.Id == id.ToString()).ToList();

    foreach (var clubApplicatonUser in clubApplicatonUsersToRemove)
    {
        _context.ClubApplicationUser.Remove(clubApplicatonUser);
    }

    _context.ClubApplicationUser.Add(new ClubApplicationUser()
    {
        Id = id.ToString(),
        ClubID = AssignClubUser.SelectedClubID
    });

    await _context.SaveChangesAsync();

    return Page();
}

如果您不想删除任何内容,而只需添加新记录:

public async Task<IActionResult> OnPostAsync(Guid id)
{
    if (!this.ModelState.IsValid)
    {
        return Page();
    }

    // dont delete, just add new one
    var clubApplicatonUserExists = await _context.ClubApplicationUser
        .Where(m => m.Id == id.ToString() && m.ClubID == AssignClubUser.SelectedClubID).FirstOrDefaultAsync();


    if (clubApplicatonUserExists == null)
    {
        _context.ClubApplicationUser.Add(new ClubApplicationUser()
        {
            Id = id.ToString(),
            ClubID = AssignClubUser.SelectedClubID
        });

        await _context.SaveChangesAsync();
    }

    return Page();
}

答案 3 :(得分:1)

如果您对数据库架构有控制权,另一种解决方案是向链接表添加一个多余的键。

然后,如果您需要更新俱乐部或用户,则无需更改实体唯一标识符,因此将被允许。