I am learning ASP.NET Core MVC and my model is
namespace Joukyuu.Models
{
public class Passage
{
public int PassageId { get; set; }
publi
What attributes I have to attach to the CreatedDate and ModifiedDate properties to make them automatically populated by the server based on the above scenario?
Solution 1)
namespace Joukyuu.Models
{
public class Passage
{
public int PassageId { get; set; }
public string Contents { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime ModifiedDate { get; set; }
public Passage()
{
this.CreatedDate = DateTime.UtcNow;
this.ModifiedDate = DateTime.UtcNow;
}
}
}
and by edit you have to change/update it by your self!
Solution 2)
Custom attribute:
[SqlDefaultValue(DefaultValue = "getutcdate()")]
public DateTime CreatedDate { get; set; }
Entity Framework 6 Code first Default value
Solution 3)
with help of Computed:
[Required, DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreatedUtc { get; set;
"dbo.Products",
c => new
{
ProductId = c.Int(nullable: false, identity: true),
Name = c.String(),
CreatedUtc = c.DateTime(nullable: false, defaultValueSql: "GETUTCDATE()"),
})
.PrimaryKey(t => t.ProductId);
https://andy.mehalick.com/2014/02/06/ef6-adding-a-created-datetime-column-automatically-with-code-first-migrations/
Solution 4) You can also do this with command interceptor by modifying manually the query.
Solution 5) Use Repository pattern to manage the data creation and set it by CreateNew This is my favour Solution!
https://msdn.microsoft.com/en-us/library/ff649690.aspx
Solution 6) just set it or get in in the UI or in your VM.
In Entity Framework Core 1.0 easy:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Passage>()
.Property(b => b.CreatedDate )
.HasDefaultValueSql("getdate()");
}
An alternative to using SaveChangesAsync is you can create database triggers for the respective database. I made a gist with some helper functions for mssql, postgress, sqlite, and mysql. Here is a paste of the code.
using Microsoft.EntityFrameworkCore.Metadata;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace ProjectNamespace.Api.Utils
{
public class DatabaseTools
{
public class DatabaseKeys
{
public string ValueGenerationStrategy;
public object SerialColumn;
public string DEFAULTDATE_CREATE;
public string DEFAULTDATE_UPDATE;
public string dateTime;
public bool UpdateDateTrigger = true;
public string ProviderName { get; set; }
}
/*
Notes:
can check if updateDateTrigger needed for dataprovider and add update triggers
if (dbKeys.UpdateDateTrigger)
{
migrationBuilder.Sql(DatabaseTools.getUpdateDateTrigger(dbKeys.ProviderName, "TG_roles_updated_at", "roles", "updated_at"));
}
if postgress don't forget to drop the generated functions for each of the different named update columns (updated_at, modified_date) at the end of the migration E.g.
protected override void Down(MigrationBuilder migrationBuilder){...
if (dbKeys.ProviderName == "postgress")
{
migrationBuilder.Sql(@"
DROP FUNCTION update_" + "updated_at" + @"_column();
");
}
postgres doesn't support parameters in triggers for new and old keywords.
*/
/// <summary>
/// needed for non mysql implementation to update a datetime on a record update
/// </summary>
/// <param name="databaseProvider"></param>
/// <param name="name"></param>
/// <param name="table"></param>
/// <param name="column"></param>
/// <param name="schema"></param>
/// <param name="id_column"></param>
/// <returns></returns>
public static string getUpdateDateTrigger(string databaseProvider, string name, string table, string column, string schema = "", string id_column = "id")
{
string updateDateTrigger = null;
if (!string.IsNullOrEmpty(schema))
schema = schema + ".";
switch (databaseProvider)
{
case "sqlite":
updateDateTrigger = @"
CREATE TRIGGER [" + name + @"]
AFTER
UPDATE
ON " + table + @"
FOR EACH ROW
WHEN NEW." + column + @" <= OLD." + column + @"
BEGIN
update " + table + @" set " + column + @"=CURRENT_TIMESTAMP where " + id_column + @"=OLD." + id_column + @";
END
";
break;
case "postgress":
updateDateTrigger = @"
CREATE OR REPLACE FUNCTION update_" + column + @"_column() RETURNS TRIGGER AS
$$
BEGIN
NEW.""" + column + @""" = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER " + name + @"
BEFORE UPDATE ON " + schema + @"""" + table + @"""
FOR EACH ROW EXECUTE PROCEDURE update_" + column + @"_column();
";
break;
case "sqlserver":
updateDateTrigger = @"
CREATE TRIGGER " + name + @"
ON " + table + @"
AFTER UPDATE AS
UPDATE " + table + @"
SET " + column + @" = GETDATE()
WHERE " + id_column + @" IN (SELECT DISTINCT " + id_column + @" FROM Inserted);
";
break;
}
return updateDateTrigger;
}
/*
Notes:
if in OnModelCreating can call as var dbKeys = DatabaseTools.getDatabaseDefaults(this.Database.ProviderName);
if in a migration call as var dbKeys = DatabaseTools.getDatabaseDefaults(migrationBuilder.ActiveProvider);
Then update any datetime columns for correct defaults
created_at = table.Column<DateTime>(type: dbKeys.dateTime, nullable: true, defaultValueSql: dbKeys.DEFAULTDATE_CREATE),
updated_at = table.Column<DateTime>(type: dbKeys.dateTime, nullable: true, defaultValueSql: dbKeys.DEFAULTDATE_UPDATE)
*/
/// <summary>
/// Can get specific formats for different databases. Supports mssql, postgress, sqlite, and mysql.
/// </summary>
/// <param name="databaseProvider"></param>
/// <returns></returns>
public static DatabaseKeys getDatabaseDefaults(string databaseProvider)
{
var dbKeys = new DatabaseKeys();
dbKeys.dateTime = "datetime";
switch (databaseProvider)
{
case "Microsoft.EntityFrameworkCore.Sqlite":
case "sqlite":
dbKeys.DEFAULTDATE_UPDATE = "datetime('now')";
dbKeys.SerialColumn = true;
dbKeys.ProviderName = "sqlite";
dbKeys.ValueGenerationStrategy = "Sqlite:Autoincrement";
break;
case "postgress":
case "Npgsql.EntityFrameworkCore.PostgreSQL":
dbKeys.DEFAULTDATE_UPDATE = "CURRENT_TIMESTAMP";
dbKeys.SerialColumn = NpgsqlValueGenerationStrategy.SerialColumn;
dbKeys.ProviderName = "postgress";
dbKeys.ValueGenerationStrategy = "Npgsql:ValueGenerationStrategy";
dbKeys.dateTime = "timestamp";
break;
case "mysql":
case "MySql.Data.EntityFrameworkCore":
dbKeys.DEFAULTDATE_UPDATE = "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP";
dbKeys.DEFAULTDATE_CREATE = "CURRENT_TIMESTAMP";
dbKeys.UpdateDateTrigger = false;
dbKeys.SerialColumn = true;
dbKeys.ProviderName = "mysql";
dbKeys.ValueGenerationStrategy = "MySQL:AutoIncrement";
break;
case "sqlserver":
default:
dbKeys.DEFAULTDATE_UPDATE = "(getdate())";
dbKeys.SerialColumn = SqlServerValueGenerationStrategy.IdentityColumn;
dbKeys.ProviderName = "sqlserver";
dbKeys.ValueGenerationStrategy = "SqlServer:ValueGenerationStrategy";
break;
}
if (string.IsNullOrEmpty(dbKeys.DEFAULTDATE_CREATE))
dbKeys.DEFAULTDATE_CREATE = dbKeys.DEFAULTDATE_UPDATE;
return dbKeys;
}
}
}
Solution for CreationDate in PostgreSQL:
builder.Property(e => e.CreationDate)
.HasColumnType("timestamp without time zone")
.HasDefaultValueSql("NOW()")
.ValueGeneratedOnAdd();
via: https://www.npgsql.org/efcore/modeling/generated-properties.html#guiduuid-generation Unfortunately there is not solution for update event
If your are using Code first you could try this
[Required, DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreatedDate { get; set; }
On Migration
AddColumn("Passage", "CreatedDate", n => n.DateTime(nullable: false, defaultValueSql: "GETDATE()"));
More reference here,similar answer
Or you can global override the
saveChanges
Note* This will affect on the entire model if you have theCreatedDate
field
public override int SaveChanges()
{
DateTime saveTime = DateTime.Now;
foreach (var entry in this.ChangeTracker.Entries()
.Where(e => e.State == (EntityState) System.Data.EntityState.Added))
{
if (entry.Property("CreatedDate").CurrentValue == null)
entry.Property("CreatedDate").CurrentValue = saveTime;
}
return base.SaveChanges();
}
For those who are using the asynchronous system (SaveChangesAsync
) and .NET Core, it's better to override the DbContext
's SaveChangesAsync
method:
public override Task<int> SaveChangesAsync(
bool acceptAllChangesOnSuccess,
CancellationToken cancellationToken = default(CancellationToken))
{
var AddedEntities = ChangeTracker.Entries()
.Where(E => E.State == EntityState.Added)
.ToList();
AddedEntities.ForEach(E =>
{
E.Property("CreationTime").CurrentValue = DateTime.Now;
});
var EditedEntities = ChangeTracker.Entries()
.Where(E => E.State == EntityState.Modified)
.ToList();
EditedEntities.ForEach(E =>
{
E.Property("ModifiedDate").CurrentValue = DateTime.Now;
});
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
Also, you can define a base class or an interface for your models with these properties:
public class SaveConfig
{
public DateTime CreationTime { get; set; }
public DateTime? ModifiedDate { get; set; }
}
Its too easy.Just you should do 2 step:
1.create model with these fields on top of createDate
field:
[Required, DatabaseGenerated(DatabaseGeneratedOption.Computed)]
2.After create migration and in its file befor update database edit column of this part and add defaultValueSql: "getdate()"
like this:
id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
CreatedDate = table.Column<DateTime>(defaultValueSql: "getdate()",nullable: false)