0

We have defined a base class as

public class BaseSchema
{
    [Key]
    [ScaffoldColumn(false)]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    
    public bool IsDeleted { get; set; }
    public DateTime PerformedOn { get; set; }
}

Deriving all other classed form this Base schema. Sample class is

public class HoodAudit : BaseSchema{}

After adding HoodAudit in datacontext class and runing migration, migration file generated as

CreateTable(
                "dbo.HoodAudits",
                c => new
                    {
                        Id = c.Int(nullable: false, identity: true),
                       //Other properties
                        IsDeleted = c.Boolean(nullable: false),
                        PerformedOn = c.DateTime(nullable: false),
                    },

We want to make PerformedOn as PerformedOn = c.DateTime(nullable: false, defaultValueSql: "GETDATE()"), and IsDeleted as IsDeleted = c.Boolean(nullable: false, defaultValueSql: "0"),

Is there any way we can achieve this? As we are deriving all other classes from BaseSchema, would be great if you suggest any generic way which will apply in al its derived classes. Thank You!

Oxygen
  • 831
  • 4
  • 17
  • 42

1 Answers1

0

As far as I know, there is no attribute to specify a default database value (SQL calculated or otherwise) on your entity property, instead you need to add the information to your "OnModelCreating" method of your DbContext class and it'll use that info for the migration. You'll find there are many powerful things you can do OnModelCreating that you can't do with attributes...

Here are two sets of examples, one for EF 6, one for EF Core.

Single entity property configuration in EF 6

public class MyDbContext : Microsoft.EntityFrameworkCore.DbContext
{
    protected override void OnModelCreating(DbModelBuilder builder)
    {
        base.OnModelCreating(builder);

        modelBuilder.Entity<MyInheritedEntityType>()
            .Property(p => p.PerformedOn)
            .HasColumnAnnotation("SqlDefaultValue", "getDate()");
    }
}

To do this for ALL entities inheriting from BaseSchema in EF 6, there are three steps:

  1. Create an attribute to flag the property needing to be configured in your base class:
/// <summary>
/// Simply an empty attribute that specifies the field should be defaulted to GetDate() in DB.
/// </summary>
[System.AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
class DefaultToNowAttribute: Attribute
{
}
  1. Apply the attribute to any DateTime properties (including in base classes) you want to default to GetDate()
class BaseSchema
{
    [Key]
    [ScaffoldColumn(false)]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public bool IsDeleted { get; set; }
    [DefaultToNow]
    public DateTime PerformedOn { get; set; }
}
  1. Use the following in OnModelCreating:
modelBuilder.Properties<DateTime>()
    .Where(x => x.CustomAttributes.Any(y => y.AttributeType == typeof(DefaultToNowAttribute)))
    .Configure(x => x.HasColumnAnnotation("SqlDefaultValue", "getDate()"));

Single Entity Example in EF Core 5:

public class MyDbContext : Microsoft.EntityFrameworkCore.DbContext
{
    protected override void OnModelCreating(DbModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<HoodAudits>(ha =>
            ha.Property(p => p.PerformedOn).HasDefaultValueSql("GETDATE()"));
    }
}

To do this for ALL entities inheriting from BaseSchema in EF Core 5 you can use the following in OnModelCreating:

var contextType = typeof(MyDbContext);
// A really involved reflection query
foreach (var inerhitsFromBaseSchema in
    System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
        .Where(x => typeof(BaseSchema).IsAssignableFrom(x) // Inherits BaseSchema
            && contextType.FindMembers( // And is a DbSet<> in the context
                System.Reflection.MemberTypes.Property, 
                System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance, 
                (y, z) =>
                    ((System.Reflection.PropertyInfo)y).GetGetMethod().ReturnType.IsGenericType
                    && ((System.Reflection.PropertyInfo)y).GetGetMethod().ReturnType.GetGenericTypeDefinition() == typeof(DbSet<>)
                    && ((System.Reflection.PropertyInfo)y).GetGetMethod().ReturnType.GetGenericArguments()[0] == x,
                null).Length > 0)) // End And is a DbSet<> in the context
    // To run one line against each found object.
    builder.Entity(inheritsFromBaseSchema).Property("PerformedOn")
        .HasDefaultValueSql("GETDATE()");
Lacutah
  • 276
  • 1
  • 7
  • Thank you for your suggestion. In this case we need to do this for every new class which will be derived from Baseschema. Looking for something which can be added only once for Basechema so that it will automatically applied for its derived classes. – Oxygen Feb 02 '21 at 14:18
  • @Oxygen, I updated the answer accordingly to show how to apply to all derived objects, does that answer your question? – Lacutah Feb 02 '21 at 19:49
  • It seems the generic way. When I tried this code getting error on last lin as No overload for method 'Entity' takes 1 arguments. I am using ef 6. – Oxygen Feb 03 '21 at 02:59
  • Oh boy, I did mess up - EF Core instead of EF 6, I know I have some generic code around somewhere before I updated to EF Core... My curiosity has me searching. – Lacutah Feb 03 '21 at 04:29
  • OK, added an example for EF 6 using attributes (that you can configure based on). EF6 doesn't have the nice helper methods to configure entities that EF Core has... – Lacutah Feb 03 '21 at 04:59
  • It generated code below but does not make column with default value.I tried by inserting records from ssms without PerformedOn,its is expecting to provide value. Generated code-PerformedOn = c.DateTime(nullable: false, annotations: new Dictionary { { "SqlDefaultValue", new AnnotationValues(oldValue: null, newValue: "getDate()") }, }), – Oxygen Feb 03 '21 at 17:22
  • Oh, I see. Changing it to defaultValueSql doesn't do it either. The only solutions I've seen are utilizing custom SQL generation for EF6... For your purposes, would it be simpler to simply set the default value on the entity itself? ```Public DateTime PreformedOn { get; set; } = DateTime.Now();``` - true, doesn't set a default value in SQL server, but whose inserting data manually there anyway? – Lacutah Feb 03 '21 at 19:21
  • For reference (that makes MY head spin...) https://stackoverflow.com/questions/19554050/entity-framework-6-code-first-default-value/27920032 – Lacutah Feb 03 '21 at 19:22
  • Thank you so much for your time and references. Learnt new things.. – Oxygen Feb 05 '21 at 02:32