需要帮助更新模型



我有一个名为ClubApplicationUser的模型,它是ClubApplicationUser间的桥梁,是身份用户模型的扩展模型:

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.2Razor Pages

更新-1:

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

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

我们可以毫无问题地更新它。所以它看起来像是.Net核心模型中的约束。

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

如果你从你的域的逻辑来看,我不认为用户通常会从一个俱乐部的成员变成另一个俱乐部,他们会离开(删除)一个俱乐部并加入(插入)另一个。

虽然我可以提出另一个更新有意义的领域,所以我认为这不是一个好的通用论点。

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

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');

*这不起作用-请参阅注释*

我的第三个选择是你最快实施的,但我不确定所有的影响。

如果您更改OnModelCreating以设置索引而不是密钥

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

成为

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

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

关于:

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();
}

如果您可以控制数据库模式,另一个解决方案是向链接表添加一个替代键。

然后,如果你需要更新俱乐部或用户,你不会更改实体的唯一标识符,因此它将被允许。

最新更新