C
C#6mo ago
baristaner

DateTime.UtcNow returns 0001-01-01T00:00:00

Hello guys I have BaseEntity and it has createdAt,modifiedAt values in it So the problem is when i run this code it returns
"createdAt": "2023-12-27T13:57:08.3933333",
"modifiedAt": "0001-01-01T00:00:00"
"createdAt": "2023-12-27T13:57:08.3933333",
"modifiedAt": "0001-01-01T00:00:00"
But i want the values to be same when it's first created
c#
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
DateTime now = DateTime.UtcNow;

foreach (var entry in ChangeTracker.Entries<BaseEntity>())
{
switch (entry.State)
{
case EntityState.Added:
if (entry.Entity.CreatedAt == DateTime.MinValue)
{
entry.Entity.CreatedAt = now;
}
entry.Entity.ModifiedAt = now;
break;

case EntityState.Modified:
entry.Entity.ModifiedAt = now;
break;
}
Console.WriteLine($"Entity Type: {entry.Entity.GetType().Name}, State: {entry.State}, CreatedAt: {entry.Entity.CreatedAt}, ModifiedAt: {entry.Entity.ModifiedAt}");
}

return await base.SaveChangesAsync(cancellationToken);
}
}
c#
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
DateTime now = DateTime.UtcNow;

foreach (var entry in ChangeTracker.Entries<BaseEntity>())
{
switch (entry.State)
{
case EntityState.Added:
if (entry.Entity.CreatedAt == DateTime.MinValue)
{
entry.Entity.CreatedAt = now;
}
entry.Entity.ModifiedAt = now;
break;

case EntityState.Modified:
entry.Entity.ModifiedAt = now;
break;
}
Console.WriteLine($"Entity Type: {entry.Entity.GetType().Name}, State: {entry.State}, CreatedAt: {entry.Entity.CreatedAt}, ModifiedAt: {entry.Entity.ModifiedAt}");
}

return await base.SaveChangesAsync(cancellationToken);
}
}
28 Replies
Pobiega
Pobiega6mo ago
Seems like a faulty conclusion you are making there. Have you verified that the state truly is Added or Modified in the case above?
SogdianWarrior
SogdianWarrior6mo ago
Looks like DateTime.UtcNow isn't the issue, since it has already assigned its value to CreatedAt, but like obiega already mentioned, are you sure the state is trully Added or Modified? Because it's possible that the state is actually none of the two, and CreatedAt has been given a value somewhere else outside of this SaveChangesAsync function Can you show the piece of code where the SaveChangesAsync function is being called from? @baristaner
baristaner
baristaner6mo ago
Sure
c#
public async Task UpdateCode(int id, string newCode, bool isActive)
{
var codeToUpdate = await _context.Codes.FindAsync(id);

if (codeToUpdate != null)
{
codeToUpdate.Code = newCode;
codeToUpdate.IsActive = isActive;
//codeToUpdate.ModifiedAt = DateTime.UtcNow;

await _context.SaveChangesAsync();
}
}
c#
public async Task UpdateCode(int id, string newCode, bool isActive)
{
var codeToUpdate = await _context.Codes.FindAsync(id);

if (codeToUpdate != null)
{
codeToUpdate.Code = newCode;
codeToUpdate.IsActive = isActive;
//codeToUpdate.ModifiedAt = DateTime.UtcNow;

await _context.SaveChangesAsync();
}
}
So this has no issues But the problem is here
c#
public void GenerateCodes()
{
_context.Database.ExecuteSqlRaw("EXEC dbo.GenerateCodes");
}
c#
public void GenerateCodes()
{
_context.Database.ExecuteSqlRaw("EXEC dbo.GenerateCodes");
}
I've never called the function in here tho
Pobiega
Pobiega6mo ago
well uh thats you activating a stored procedure via raw SQL
SogdianWarrior
SogdianWarrior6mo ago
Okay so, have you verified whether the state is either Added / Modified?
Pobiega
Pobiega6mo ago
the changetracker is not aware of any changes done via rawsql or via stored procedures
SogdianWarrior
SogdianWarrior6mo ago
yeah it's a stored procedure, why should that even assign a value to modifiedAt from the code? unless you have a piece of SQL code that assigns a value to modifiedAt, but let's keep that aside Your question is about the UpdateCode function, updating values, and not seeing the expected value back in the database
baristaner
baristaner6mo ago
Stored Procedure does nothing to that entity it inserts values into Codes table And the BaseTable is basically just the default values like Id,createdAt,modifiedAt
"createdAt": "2023-12-27T13:57:08.3933333",
"modifiedAt": "0001-01-01T00:00:00"
"createdAt": "2023-12-27T13:57:08.3933333",
"modifiedAt": "0001-01-01T00:00:00"
createdAt is correct but modifiedAt is not it creates the timestamp perfectly except one thing. So when i run this procedure it will automatically create these fields
SogdianWarrior
SogdianWarrior6mo ago
have you debugged your code and checked the code step-by-step inside the savechangesasync? have you confirmed that the state is either added or modified?
baristaner
baristaner6mo ago
Trying that rn brb
SogdianWarrior
SogdianWarrior6mo ago
yes plz
baristaner
baristaner6mo ago
c#
Entity Type: Codes, State: Modified, CreatedAt: 12/27/2023 1:47:38 PM, ModifiedAt: 12/27/2023 11:23:03 AM
c#
Entity Type: Codes, State: Modified, CreatedAt: 12/27/2023 1:47:38 PM, ModifiedAt: 12/27/2023 11:23:03 AM
I just ran UpdateCode()
SogdianWarrior
SogdianWarrior6mo ago
okay, so it does assign a value to it and in the database?
baristaner
baristaner6mo ago
Yep But not when i run the stored Procedure
SogdianWarrior
SogdianWarrior6mo ago
well then i need to see your stored procedure sql statement
baristaner
baristaner6mo ago
Not the modifiedAt i mean
SogdianWarrior
SogdianWarrior6mo ago
if you could copy your sql statement here
baristaner
baristaner6mo ago
Sure mate
ALTER PROCEDURE [dbo].[GenerateCodes]
@NumOfCodes INT = 50,
@CharacterSet NVARCHAR(50) = '13456789ACDEFHKLMNPQRTVWXYZ',
@ExpirationMonths INT = 3,
@ExpirationDate DATE = NULL /* TIME FORMAT ; YYYY-MM-DD */
AS
BEGIN
SET NOCOUNT ON;
DECLARE @Counter INT = 1;

BEGIN TRY
BEGIN TRANSACTION;

WHILE @Counter <= @NumOfCodes
BEGIN
DECLARE @RandomString VARCHAR(10);
SET @ExpirationDate = DATEADD(MONTH, 3, GETDATE());
SET @RandomString = '';

DECLARE @InnerCounter INT = 1;
WHILE @InnerCounter <= 10
BEGIN
SET @RandomString = @RandomString + SUBSTRING(@CharacterSet, (ABS(CHECKSUM(NEWID())) % LEN(@CharacterSet) + 1), 1);
SET @InnerCounter = @InnerCounter + 1;
END;

INSERT INTO dbo.Codes (CODE, ExpirationDate,createdAt,IsActive)
VALUES (@RandomString, @ExpirationDate,GETDATE(),1);

SET @Counter = @Counter + 1;
END;

COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;

PRINT 'An error occurred. Transaction rolled back.';
PRINT ERROR_MESSAGE();
END CATCH;
END;
ALTER PROCEDURE [dbo].[GenerateCodes]
@NumOfCodes INT = 50,
@CharacterSet NVARCHAR(50) = '13456789ACDEFHKLMNPQRTVWXYZ',
@ExpirationMonths INT = 3,
@ExpirationDate DATE = NULL /* TIME FORMAT ; YYYY-MM-DD */
AS
BEGIN
SET NOCOUNT ON;
DECLARE @Counter INT = 1;

BEGIN TRY
BEGIN TRANSACTION;

WHILE @Counter <= @NumOfCodes
BEGIN
DECLARE @RandomString VARCHAR(10);
SET @ExpirationDate = DATEADD(MONTH, 3, GETDATE());
SET @RandomString = '';

DECLARE @InnerCounter INT = 1;
WHILE @InnerCounter <= 10
BEGIN
SET @RandomString = @RandomString + SUBSTRING(@CharacterSet, (ABS(CHECKSUM(NEWID())) % LEN(@CharacterSet) + 1), 1);
SET @InnerCounter = @InnerCounter + 1;
END;

INSERT INTO dbo.Codes (CODE, ExpirationDate,createdAt,IsActive)
VALUES (@RandomString, @ExpirationDate,GETDATE(),1);

SET @Counter = @Counter + 1;
END;

COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;

PRINT 'An error occurred. Transaction rolled back.';
PRINT ERROR_MESSAGE();
END CATCH;
END;
SogdianWarrior
SogdianWarrior6mo ago
ok lemme check
baristaner
baristaner6mo ago
My entities Code.cs
c#
using fastwin.Entities;
using System.ComponentModel.DataAnnotations;

namespace fastwin.Models
{
public class Codes : BaseEntity
{

[StringLength(10)]
public string? Code { get; set; }

public DateTime? ExpirationDate { get; set; }

public bool IsActive { get; set; }
}
}
c#
using fastwin.Entities;
using System.ComponentModel.DataAnnotations;

namespace fastwin.Models
{
public class Codes : BaseEntity
{

[StringLength(10)]
public string? Code { get; set; }

public DateTime? ExpirationDate { get; set; }

public bool IsActive { get; set; }
}
}
Base.cs
c#
using System.ComponentModel.DataAnnotations;

namespace fastwin.Entities
{
public class BaseEntity
{
[Key]
public int Id { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime ModifiedAt { get; set; }
}
}
c#
using System.ComponentModel.DataAnnotations;

namespace fastwin.Entities
{
public class BaseEntity
{
[Key]
public int Id { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime ModifiedAt { get; set; }
}
}
SogdianWarrior
SogdianWarrior6mo ago
INSERT INTO dbo.Codes (CODE, ExpirationDate,createdAt,IsActive) VALUES (@RandomString, @ExpirationDate,GETDATE(),1); you dont insert anything into the ModifiedAt column? I do see createdAt, but not modifiedAt
baristaner
baristaner6mo ago
oh yeah i totally forget about it
SogdianWarrior
SogdianWarrior6mo ago
ALTER PROCEDURE [dbo].[GenerateCodes] @NumOfCodes INT = 50, @CharacterSet NVARCHAR(50) = '13456789ACDEFHKLMNPQRTVWXYZ', @ExpirationMonths INT = 3, @ExpirationDate DATE = NULL /* TIME FORMAT ; YYYY-MM-DD */ AS BEGIN SET NOCOUNT ON; DECLARE @Counter INT = 1; BEGIN TRY BEGIN TRANSACTION; WHILE @Counter <= @NumOfCodes BEGIN DECLARE @RandomString VARCHAR(10); SET @ExpirationDate = DATEADD(MONTH, 3, GETDATE()); SET @RandomString = ''; DECLARE @InnerCounter INT = 1; WHILE @InnerCounter <= 10 BEGIN SET @RandomString = @RandomString + SUBSTRING(@CharacterSet, (ABS(CHECKSUM(NEWID())) % LEN(@CharacterSet) + 1), 1); SET @InnerCounter = @InnerCounter + 1; END; SET @UtcNow = GETUTCDATE(); INSERT INTO dbo.Codes (CODE, ExpirationDate,CreatedAt,ModifiedAt,IsActive) VALUES (@RandomString, @ExpirationDate,@UtcNow,@UtcNow,1); SET @Counter = @Counter + 1; END; COMMIT TRANSACTION; END TRY BEGIN CATCH ROLLBACK TRANSACTION; PRINT 'An error occurred. Transaction rolled back.'; PRINT ERROR_MESSAGE(); END CATCH; END; something like this
baristaner
baristaner6mo ago
What if i call the function _context.SaveChangesAsync();
c#
public void GenerateCodes()
{
_context.Database.ExecuteSqlRaw("EXEC dbo.GenerateCodes");
}
c#
public void GenerateCodes()
{
_context.Database.ExecuteSqlRaw("EXEC dbo.GenerateCodes");
}
and remove those values in stored procedure?
SogdianWarrior
SogdianWarrior6mo ago
you can either do the code in C#, or you can let the stored procedure do it but the savechangesasync will handle the createdAt and modifiedAt
Pobiega
Pobiega6mo ago
EF (where the context and SaveChanges comes from) isn't aware of any changes done by the stored procedure you either do this with EF, or you do it with the stored procedure. Not both.
SogdianWarrior
SogdianWarrior6mo ago
if that's what @baristaner is asking, then indeed EF isn't aware of the changes done by the stored procedure.
baristaner
baristaner6mo ago
Okay gotcha thanks guys I'm trying a different approach rn