enriching enums - possible?

c# entity-framework-core enums

Question

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.

1
1
9/22/2019 6:32:37 AM

Accepted Answer

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.

2
9/22/2019 10:23:26 PM

Popular Answer

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;
    }


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow