I am looking for a solution to store immutable data in my code instead of the db. In the specific case I want to deal with units. Here an example for units of weight (they won't change, so its ok to store them in my code):
public class Unit
{
public Unit() { }
public Unit(string name, string symbol, double factor, Unit baseUnit, UnitType unitType)
{
this.Name = name;
this.Symbol = symbol;
this.Factor = factor;
this.BaseUnit = baseUnit;
this.UnitType = unitType;
}
public int Id { get; set; }
public string Name { get; set; }
public UnitType UnitType { get; set; }
public string Symbol { get; set; }
public string NamePlural { get; set; }
public Unit BaseUnit { get; set; }
public double Factor { get; set; }
}
public static class TimeUnits
{
public static Unit second = new Unit("second", "s", 1, null, UnitTypes.Time);
public static Unit microsecond = new Unit("microsecond", "μs", 0.000001, second, UnitTypes.Time);
public static Unit millisecond = new Unit("millisecond", "ms", 0.001, second, UnitTypes.Time);
public static Unit minute = new Unit("minute", "min", 60.0, second, UnitTypes.Time);
public static Unit hour = new Unit("hour", "h", 3600.0, second, UnitTypes.Time);
public static Unit day = new Unit("day", "d", 24.0, hour, UnitTypes.Time);
public static Unit week = new Unit("week", "w", 7, day, UnitTypes.Time);
}
As said I do not want to store these immutable information in the db in order to avoid additional unions for ef-core to do when retrieving data from the db.
In the case of gender I simply use an enum:
public enum Gender
{
male = 1,
female = 2,
not_applicable = 9,
dont_want_to_share = 10
}
I would like to have a similar solution for the units. But an enum has only an Id and a Name. For things like the units shown above or other cases I need additional properties (ex. factore, unitType etc.). Thanks a lot for any hint on how I could achieve this so that ef core loads those values like it does with enums.
Attributes is one approach, but if you want to avoid using reflection, you can implement a class that holds everything you need, something similar to this :
public enum UnitType
{
Second,
Microsecond,
Millisecond,
Minute,
Hour,
Day,
Week
}
public class Unit
{
public string Name { get; private set; }
public string Symbol { get; private set; }
public double Factor { get; private set; }
public Unit Base { get; private set; }
public Unit(UnitType unit, bool isBase = false)
{
Name = GetUnitName(unit);
Symbol = GetUnitSymbol(unit);
Factor = GetUnitFactor(unit);
if (!isBase)
Base = GetUnitBase(unit);
}
private string GetUnitName(UnitType unit)
{
switch (unit)
{
case UnitType.Second:
return "second";
case UnitType.Microsecond:
return "microsecond";
case UnitType.Millisecond:
return "millisecond";
case UnitType.Minute:
return "minute";
case UnitType.Hour:
return "hour";
case UnitType.Day:
return "day";
case UnitType.Week:
return "week";
default:
return null;
}
}
private string GetUnitSymbol(UnitType unit)
{
switch (unit)
{
case UnitType.Second:
return "s";
case UnitType.Microsecond:
return "μs";
case UnitType.Millisecond:
return "ms";
case UnitType.Minute:
return "min";
case UnitType.Hour:
return "h";
case UnitType.Day:
return "d";
case UnitType.Week:
return "w";
default:
return null;
}
}
private double GetUnitFactor(UnitType unit)
{
switch (unit)
{
case UnitType.Second:
return 1;
case UnitType.Microsecond:
return 0.000001;
case UnitType.Millisecond:
return 0.001;
case UnitType.Minute:
return 60.0;
case UnitType.Hour:
return 3600.0;
case UnitType.Day:
return 24.0;
case UnitType.Week:
return 7;
default:
return 0;
}
}
private Unit GetUnitBase(UnitType unit)
{
switch (unit)
{
case UnitType.Microsecond:
return new Unit(UnitType.Second, true);
case UnitType.Millisecond:
return new Unit(UnitType.Second, true);
case UnitType.Minute:
return new Unit(UnitType.Second, true);
case UnitType.Hour:
return new Unit(UnitType.Minute, true);
case UnitType.Day:
return new Unit(UnitType.Hour, true);
case UnitType.Week:
return new Unit(UnitType.Day, true);
default:
return null;
}
}
}
usage :
// initiate a new Unit instance.
var unit = new Unit(UnitType.Week);
// Get values
var name = unit.Name;
var symbol = unit.Symbol;
var factor = unit.Factor;
// In case if some units doesn't have base
if (unit.Base != null)
{
var baseName = unit.Base.Name;
var baseSymbol = unit.Base.Symbol;
var baseFactor = unit.Base.Factor;
}
This is just a simple example and not fully tested, it's just to show you another approach that could be implemented.
You can also use implicit operator to get the value Example :
public class Unit
{
......
public static implicit operator double(Unit v) => v.Factor;
public static implicit operator string(Unit v) => v.Symbol;
}
Getting the value implicitly :
var symbol = (string) unit; // will return the v.Symbol
var factor = (double) unit; // will return the v.Factor
Also, I have not showing the UnitTypes.Time
since there is no much code about it, but I think the samples and ideas will be enough to give your thoughts the push you need.
I am not sure what you exatly want to do but I think attributes will be a good option. You could set attributes on every Unit Enum Fields like this one, and retrieve them by reflection any time it is needed.
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
sealed class UnitTypeAttribute : Attribute
{
public UnitType UnitType{ get; set; }
public UnitAttribute(UnitType unitT)
{
UnitType= unitT;
}
}
enum Unit
{
[UnitType(UnitTypes.Time)]
Second,
[UnitType(UnitTypes.Time)]
MicroSecond,
[UnitType(UnitTypes.Time)]
Hour
}
and then, when you want to retrieve it, use this method (can be made generic)
public static UnitType GetUnitTypeAttribute(Unit unit)
{
var memberInfo = typeof(Unit).GetMember(unit.ToString());
var result = memberInfo[0].GetCustomAttributes<UnitTypeAttribute>(false)
return ((UnitType)result).UnitType;
}