patterncsharpMinor
Setting Entity Framework up for success
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
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
First: seeding. It allows me to easily seed the default settings of the application:
Obviously this is nice. It makes seeding a breeze.
Next: it makes it nice to locate a setting from the DB:
You can't bork a string name this way (you can, but you don't need to). You can just say `Setting.D
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
This one will hold each setting as a property decorated with the
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
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.
Lastly the
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.