public class ApplicationUser : IdentityUser
{
[Required]
[DefaultValue(5)]
public int ViewerRange { get; set; }
[DefaultValue(false)]
[Required]
public bool IsTACAccepted { get; set; }
public DateTime? TACAccepted { get; set; }
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime Created { get; set; }
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime Modified { get; set; }
public string Role { get; set; }
public ApplicationUser()
{
Modified = DateTime.Now;
Created = DateTime.Now;
}
}
Here´s the part of Register.cshtml.cs
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = Input.Email,
Email = Input.Email,
Role = Input.Role,
Created = DateTime.Now,
Modified = DateTime.Now,
IsTACAccepted = false,
ViewerRange = 5
};
var result = await _userManager.CreateAsync(user, Input.Password);
//[...]
When registering, the CreateAsync Method fails with the following error:
InvalidOperationException: The value for property 'ApplicationUser.Modified' cannot be set to null because its type is 'System.DateTime' which is not a nullable type.
I can't understand why this is failing.
Can someone explain in detail please?
Kind regards
The reason for the error is that [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
says its value is generated by the database, so any assigned value should be ignored. The error shows this isn't the case though.
This isn't the only oddity in the code. [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
on a DateTime makes no sense. IDENTITY is used to autoincrement numeric fields. This has no meaning for a DateTime column.
f you want to specify the values yourself, remove the DatabaseGenerated attributes and use them as normal fields. You can avoid the explicit assignment by specifying a default property value, eg
public DateTime Created { get; set; } = DateTime.Now;
public DateTime Modified { get; set; } = DateTime.Now;
It's also possible to configure the database to generate those values. This is described in the docs, in the Generated Values article. Adding Created
and Modified
columns is described in the Date/time generation section.
A default value constraint for the Created
column can be specified with HasDefaultValueSql
:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Created)
.HasDefaultValueSql("getdate()");
}
The Modified
timestamp needs a trigger though, which can't be specified in a DbContext's model. This must be created in the database, either directly :
CREATE TRIGGER [dbo].[Blogs_UPDATE] ON [dbo].[Blogs]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF ((SELECT TRIGGER_NESTLEVEL()) > 1) RETURN;
DECLARE @Id INT
SELECT @Id = INSERTED.BlogId
FROM INSERTED
UPDATE dbo.Blogs
SET LastUpdated = GETDATE()
WHERE BlogId = @Id
END
Or through a raw SQL migration that executes the same script :
migrationBuilder.Sql(
@"
EXEC ('CREATE TRIGGER [dbo].[Blogs_UPDATE] ON [dbo].[Blogs]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF ((SELECT TRIGGER_NESTLEVEL()) > 1) RETURN;
DECLARE @Id INT
SELECT @Id = INSERTED.BlogId
FROM INSERTED
UPDATE dbo.Blogs
SET LastUpdated = GETDATE()
WHERE BlogId = @Id
END')");