This is my solution. I used it in a EF Core DbContext in a .Net Core 3.1 DLL. It works with automatic migrations and is using an attribute, which is searched with reflection. The attribute contains the default sql value, which is then set in OnModelCreating().
- Step 1 - Add a new Attribute
[AttributeUsage(AttributeTargets.Property)]
public class DefaultValueSqlAttribute : Attribute
{
public string DefaultValueSql { get; private set; } = "";
public DefaultValueSqlAttribute(string defaultValueSql)
{
DefaultValueSql = defaultValueSql;
}
}
- Step 2 - Use that Attribute in your data classes
public class Entity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("Id", Order=0)]
[DefaultValueSql("newId()")]
public Guid Id { get; set; }
[Column("DateCreated", Order = 100)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[DefaultValueSql("GETUTCDATE()")]
public DateTime DateCreated { get; set; } = DateTime.UtcNow;
}
[Table("tbl_User")]
public class User: Entity
{
[Required]
[Column(Order = 1)]
public string EMail { get; set; }
[Column(Order = 2)]
public string Name { get; set; }
[Column(Order = 3)]
public string Forename { get; set; }
[Column(Order = 4)]
public string Street { get; set; }
[Column(Order = 5)]
public string Postalcode { get; set; }
[Column(Order = 6)]
public string MobileNumber { get; set; }
}
- Step 3 - Add your classes to the DbContext
public DbSet<User> tbl_User { get; set; }
- Step 4 - Add this code your DbContext (you have to change it to your needs...) It assumes, that all relevant data classes live in one certain assembly and one certain namespace. In my case it is a .NetStandard 2.0 DLL.
protected override void OnModelCreating(ModelBuilder mb)
{
//Uncomment this line, if you want to see what is happening when you fire Add-Migration
//Debugger.Launch();
base.OnModelCreating(mb);
OnModelCreatingAddDefaultSqlValues(mb);
}
private void OnModelCreatingAddDefaultSqlValues(ModelBuilder mb)
{
var assemblyName = "Ik.Shared";
var nameSpace = "Ik.Shared.Entities";
var asm = Assembly.Load(assemblyName);
//Read all types in the assembly Ik.Shared, that are in the namespace Ik.Shared.Entities
List<Type> types = asm.GetTypes().Where(p => p.Namespace == nameSpace).ToList();
//Read all properties in DatabaseContext, that are of type DbSet<>
var dbSets = typeof(DatabaseContext).GetProperties().Where(p => p.PropertyType.Name.ToLower().Contains("dbset")).ToList();
//A list of types, that are used as a generic argument in a DbSet<T>
List<Type> dbSetTypes = new List<Type>();
foreach (PropertyInfo pi in dbSets)
{
//Add the type of the generic argument from DbSet<T>
dbSetTypes.Add(pi.PropertyType.GetGenericArguments()[0]);
}
//For all types in Ik.Shared
foreach (Type t in types)
{
//If a type inherited from Entity AND the type itself is not Entity AND the type was used as DbSet<Type> in the DbContext
if (typeof(Entity).IsAssignableFrom(t) && t.Name != nameof(Entity) && dbSetTypes.Contains(t))
{
//Get all properties of that type
var properties = t.GetProperties().ToList();
foreach (var p in properties)
{
//Check if the property has the DefaultValueSqlAttribute
var att = p.GetCustomAttribute<DefaultValueSqlAttribute>();
if (att != null)
{
//If any property has the DefaultValueSqlAttribute, set the the value here. Done.
mb.Entity(t).Property(p.Name).HasDefaultValueSql(att.DefaultValueSql);
}
}
}
}
}
- Step 5 - Fire up a new migration in the packager consoles of Visual Studio
Add-Migration MI_000000 -StartupProject Ik.Ws.Login
- Step 6 - See the result: Look at defaultValueSql = Done
//This class was entirely created automatic. No manual changes.
public partial class MI_000000 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "tbl_User",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "newId()"),
EMail = table.Column<string>(type: "nvarchar(max)", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
Forename = table.Column<string>(type: "nvarchar(max)", nullable: true),
Street = table.Column<string>(type: "nvarchar(max)", nullable: true),
Postalcode = table.Column<string>(type: "nvarchar(max)", nullable: true),
MobileNumber = table.Column<string>(type: "nvarchar(max)", nullable: true),
DateCreated = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "GETUTCDATE()")
},
constraints: table =>
{
table.PrimaryKey("PK_tbl_User", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "tbl_User");
}
}
I hope it helps or inspires you.