HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Setting Entity Framework up for success

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
successsettingforframeworkentity

Problem

So I am working on an application (top-secret!) that I need settings for, these settings are pretty basic, but I don't want to make a single-row table with a bunch of serialized settings, or a single-row table with a bunch of columns, etc.

So I made a Setting class that's pretty handy:

public class Setting
{
    [Key]
    [MaxLength(64)]
    public string Name { get; set; }

    public string Value { get; set; }

    public string Type { get; set; }

    public Setting() { }

    public Setting(string name, string value, string type)
    {
        Name = name;
        Value = value;
        Type = type;
    }

    public static class Default
    {
        public static Setting RequireEmailValidation => new Setting("RequireEmailValidation", false.AsString(), nameof(Boolean));
    }
}


So it's a bit hinky if you're not sure why I did things.

First: I have the parameterized constructor and the default constructor, which seem to contradict. I wanted the parameterized one so I can make absolutely sure to set the required properties for a setting when I instantiate them. The default one is for Entity Framework.

Second: I have this static class Default which contains the default settings of the application. This is for two purposes:

First: seeding. It allows me to easily seed the default settings of the application:

protected override void Seed(Models.MasterDbContext context)
{
    var settings = typeof(Models.Setting.Default).GetProperties();
    foreach (var setting in settings)
    {
        context.Settings.AddOrUpdate(
            x => x.Name,
            (Models.Setting)setting.GetValue(null, null)
        );
    }

    context.SaveChanges();
}


Obviously this is nice. It makes seeding a breeze.

Next: it makes it nice to locate a setting from the DB:

_context.Settings.First(x => x.Name == Setting.Default.RequireEmailValidation.Name)


You can't bork a string name this way (you can, but you don't need to). You can just say `Setting.D

Solution

Let's make it more generic and declarative with attributes. This should upgrade your current solution.

The Setting class stays the same but I removed the Default class and instead I created a new one, the Configuration class.

This one will hold each setting as a property decorated with the DefaultValue attribute. In this example I used the context as a parameter for the constructor but this may be of course a context factory lambda or anything else that works for you best.

Anyway, the one and only place where the name of the setting is actually written down is the property name. To get the setting you use another attribute but this time a compiler attribute CallerMemberName.


Allows you to obtain the method or property name of the caller to the method.

This will pass the property name to the method that now can search for the setting. You can easily cache it if you like.

public class Configuration
{
    private readonly TestContext _context;
    public Configuration(TestContext context)
    {
        _context = context;
    }

    [DefaultValue("False")]
    public bool RequireEmailValidation
    {
        get { return GetSetting(); }
    }

    private T GetSetting([CallerMemberName] string name = null) where T : IConvertible
    {            
        var setting = _context.Settings.SingleOrDefault(s => s.Name == name);
        return setting.As();
    }
}


Lastly the Seed method need to be aware of the DefaultValue property so it needs small changes too. This means it needs to look only for properties decorated with it and then get the value from it.

protected override void Seed(Models.MasterDbContext context)
{
    var settings = 
        typeof(Configuration)
        .GetProperties()
        .Where(x => x.GetCustomAttribute() != null);
    foreach (var setting in settings)
    {
        context.Settings.AddOrUpdate(
            x => x.Name,
            setting.GetCustomAttribute().Value
        );
    }

    context.SaveChanges();
}

Code Snippets

public class Configuration
{
    private readonly TestContext _context;
    public Configuration(TestContext context)
    {
        _context = context;
    }

    [DefaultValue("False")]
    public bool RequireEmailValidation
    {
        get { return GetSetting<bool>(); }
    }

    private T GetSetting<T>([CallerMemberName] string name = null) where T : IConvertible
    {            
        var setting = _context.Settings.SingleOrDefault(s => s.Name == name);
        return setting.As<T>();
    }
}
protected override void Seed(Models.MasterDbContext context)
{
    var settings = 
        typeof(Configuration)
        .GetProperties()
        .Where(x => x.GetCustomAttribute<DefaultValueAttribute>() != null);
    foreach (var setting in settings)
    {
        context.Settings.AddOrUpdate(
            x => x.Name,
            setting.GetCustomAttribute<DefaultValueAttribute>().Value
        );
    }

    context.SaveChanges();
}

Context

StackExchange Code Review Q#151885, answer score: 2

Revisions (0)

No revisions yet.