Home > Software design >  C# - Loading data (enum stored as string) from MongoDB throw exception if cannot be mapped back to e
C# - Loading data (enum stored as string) from MongoDB throw exception if cannot be mapped back to e

Time:10-08

I need to store enums as string in MongoDB, which is fine, but I need to able to handle changes within the enum.

For example, if you have the following enum

enum CarType {
  SportCar,
  Suv,
  Hatchback
}

If I delete SUV and I try to load a record from MongoDB that is of CarType SUV, it will throw an exception, because it is unable to map it back to enum from the string. I need to be able to handle this gracefully, like have an enum value unknown and use that as a fall back value.

Is there a way to specify a custom mapper for only specify columns within MongoDB? For example the car entity has multiple string values, but only the CarType which is an enum in our back end and stored as string in our MongoDB. So when loading data from DB, use a customer mapper only for the CarType property that will not throw and exception, instead map it to CarType.Unkown

CodePudding user response:

First you need to define the unknown enum as the first enum. I myself prefer storing them as explicit ints to prevent renaming of enums corrupt the database, but that is not nessecary in your case.

enum CarType {
  Unknown
  SportCar,
  Suv,
  Hatchback
}

Then you need to write and register a custom deserializer for your enum in order to implement the fallback to "unknown" as this. It will try to parse your database value to the enum and fallbacks to default (Unknown) if unable to parse it.

 public class CustomEnumSerializer<TEnum> : MongoDB.Bson.Serialization.Serializers.EnumSerializer<TEnum>
            where TEnum : struct
        {
            public override TEnum Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
            {
                var type = context.Reader.GetCurrentBsonType();
                string val;

                switch (type)
                {
                    case BsonType.String:
                        val = context.Reader.ReadString() ?? "";
                        break;
                    case BsonType.Int32:
                        val = context.Reader.ReadInt32().ToString();
                        break;
                    case BsonType.Int64:
                        val = context.Reader.ReadInt64().ToString();
                        break;
                    case BsonType.Null:
                        return default(TEnum);
                    default:
                        return base.Deserialize(context, args);
                }

                if (Enum.TryParse(val, true, out TEnum result) && Enum.IsDefined(typeof(TEnum), result))
                {
                    return result;
                }
                return default(TEnum);
            }
        }

Register the custom serializer as:

BsonClassMap.RegisterClassMap<ClassThatHoldsTheProperty>(ms =>
            {
                ms.AutoMap();
                ms.GetMemberMap(i => i.CarTypeProperty)
                    .SetSerializer(new CustomEnumSerializer<CarType>());
            });
  • Related