I am having trouble saving a particular class with MongoDB.
My abstract class is as follow:
public abstract class Enumeration : IComparable
{
public string Name { get; }
public int Id { get; }
protected Enumeration(int id, string name)
{
Id = id;
Name = name;
}
public override string ToString() => Name;
public static IEnumerable<T> GetAll<T>() where T : Enumeration
{
var fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);
return fields.Select(f => f.GetValue(null)).Cast<T>();
}
public override bool Equals(object obj)
{
var otherValue = obj as Enumeration;
if (otherValue == null)
return false;
var typeMatches = GetType().Equals(obj.GetType());
var valueMatches = Id.Equals(otherValue.Id);
return typeMatches && valueMatches;
}
public override int GetHashCode() => Id.GetHashCode();
public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue)
{
var absoluteDifference = Math.Abs(firstValue.Id - secondValue.Id);
return absoluteDifference;
}
public static T FromValue<T>(int value) where T : Enumeration
{
var matchingItem = Parse<T, int>(value, "value", item => item.Id == value);
return matchingItem;
}
public static T FromDisplayName<T>(string displayName) where T : Enumeration
{
var matchingItem = Parse<T, string>(displayName, "display name", item => item.Name == displayName);
return matchingItem;
}
private static T Parse<T, K>(K value, string description, Func<T, bool> predicate) where T : Enumeration
{
var matchingItem = GetAll<T>().FirstOrDefault(predicate);
if (matchingItem == null)
throw new InvalidOperationException($"'{value}' is not a valid {description} in {typeof(T)}");
return matchingItem;
}
public int CompareTo(object other) => Id.CompareTo(((Enumeration)other).Id);
}
The derived class:
public class SeatState : Enumeration
{
public static SeatState Free = new SeatState(1, nameof(Free).ToLowerInvariant());
public static SeatState Booked = new SeatState(2, nameof(Booked).ToLowerInvariant());
public static SeatState Reserved = new SeatState(3, nameof(Reserved).ToLowerInvariant());
public static IEnumerable<SeatState> List() => new[] { Free, Booked, Reserved };
public SeatState(int id, string name)
: base(id, name)
{
}
public static SeatState FromName(string name)
{
var state = List().SingleOrDefault(s => string.Equals(s.Name, name, StringComparison.CurrentCultureIgnoreCase));
if (state == null)
throw new Exception($"Possible values for SeatState: {string.Join(",", List().Select(s => s.Name))}");
return state;
}
public static SeatState From(int id)
{
var state = List().SingleOrDefault(s => s.Id == id);
if (state == null)
throw new Exception($"Possible values for SeatState: {string.Join(",", List().Select(s => s.Name))}");
return state;
}
}
When I try to save a class that have SeatState as a property, mongo throw this error:
System.ArgumentOutOfRangeException: The memberInfo argument must be for class SeatState, but was for class Enumeration.
I tried also with BsonClassMap.RegisterClassMap<Enumeration>() (with and without IsRootClass) and BsonClassMap.RegisterClassMap<SeatState>()
but without success. It seems that the problem lie in the static properties, but I need them.
Any clarification in what I am doing wrong is greatly appreciated.
For others who may have the same problem, the solution is as follow:
BsonClassMap.RegisterClassMap<Enumeration>(cm =>
{
cm.SetIsRootClass(true);
cm.MapMember(m => m.Id);
cm.MapMember(m => m.Name);
});
BsonClassMap.RegisterClassMap<SeatState>(cm =>
{
cm.MapCreator(c => new SeatState(c.Id, c.Name));
});
Related
I have multiple DTO class which require type converter. The following is one of the implementations. As you will see, I need ConvertFrom only.
public class EmployeeFilterTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (typeof(string) == sourceType)
return true;
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var strVal = value as String;
if (string.IsNullOrEmpty(strVal))
return new EmployeeFilter();
EmployeeFilter employeeFilter = new EmployeeFilter();
string[] filters = strVal.Split(';');
foreach (var filter in filters)
{
var filterSplit = filter.Split(':');
if (filterSplit.Length == 2)
{
var key = filterSplit[0];
var val = filterSplit[1];
SetPropertyValue(employeeFilter, key, val);
}
}
return employeeFilter;
}
private void SetPropertyValue(EmployeeFilter employeeFilter, string key, string val)
{
var t = typeof(EmployeeFilter);
PropertyInfo[] props = t.GetProperties(BindingFlags.Instance | BindingFlags.Public);
PropertyInfo prop = props.Where(p => p.Name.Equals(key, StringComparison.CurrentCultureIgnoreCase) == true && p.CanWrite).FirstOrDefault();
if (prop != null)
prop.SetValue(employeeFilter, val);
}
}
I want to make multiple DTOs sharing the same converter in hopes of reducing code duplication as well as tests and after some researches, I have 2 problems at hand
Get the type that I want to convert in ConvertFrom method
Using Type class to initialize new object
For the first one, I don't know how to get from ITypeDescriptorContext.
For the second one, I will use the following according to this post
Type employeeType = typeof(EmployeeFilter);
object objtype = Activator.CreateInstance(employeeType);
So, how to get the type that I want to convert to?
Test case
public class testConverter
{
[Theory]
[InlineData(typeof(string), true)]
[InlineData(typeof(int), false)]
public void testCanConvertFrom(Type sourceType, bool expected)
{
//Arrange
Type randomType = typeof(Book);
Type typeGenericConverter = typeof(EmployeeFilterTypeConverter<>);
Type typeActualConverter = typeGenericConverter.MakeGenericType(randomType);
/*
1. The way of creating EmployeeFilterTypeConverter<thattype>
https://stackoverflow.com/a/266282
*/
dynamic testConverter = Activator.CreateInstance(typeActualConverter);
Mock<ITypeDescriptorContext> mockDescContext = new Mock<ITypeDescriptorContext>();
//Act
bool actual = testConverter.CanConvertFrom(mockDescContext.Object, sourceType);
//Assert
Assert.Equal(expected, actual);
}
[Theory, ClassData(typeof(TestConvertFromType1))]
/*
1. All these classdata, propertydata stuff just for passing complex objects to test
https://stackoverflow.com/a/22093968
*/
public void testConverFromType1(object value, EmployeeFilter expected)
{
//api/employee?filter=firstName:Nikhil;lastName:Doomra
//Arrange
EmployeeFilterTypeConverter<EmployeeFilter> testConverter = new EmployeeFilterTypeConverter<EmployeeFilter>();
Mock<ITypeDescriptorContext> mockDescContext = new Mock<ITypeDescriptorContext>();
//Act
EmployeeFilter actual = testConverter.ConvertFrom(mockDescContext.Object, null, value) as EmployeeFilter;
//Assert
//public static void Equal<T>(T expected, T actual);
Assert.Equal(expected, actual);
}
[Theory, ClassData(typeof(TestConvertFromType2))]
public void testConverFromType2(object value, GeoPoint expected)
{
//api/employee?filter=firstName:Nikhil;lastName:Doomra
//Arrange
EmployeeFilterTypeConverter<GeoPoint> testConverter = new EmployeeFilterTypeConverter<GeoPoint>();
Mock<ITypeDescriptorContext> mockDescContext = new Mock<ITypeDescriptorContext>();
//Act
GeoPoint actual = testConverter.ConvertFrom(mockDescContext.Object, null, value) as GeoPoint;
//Assert
//public static void Equal<T>(T expected, T actual);
Assert.Equal(expected, actual);
}
}
Test Data Model
public class TestConvertFromType1: IEnumerable<object[]>
{
private readonly List<object[]> _data = new List<object[]>
{
new object[] { "firstName:Nikhil;lastName:Doomra",
new EmployeeFilter {
FirstName = "Nikhil", LastName = "Doomra"
}},
new object[] { "firstName:Nikhil",
new EmployeeFilter {
FirstName = "Nikhil"
}}
};
public IEnumerator<object[]> GetEnumerator()
{ return _data.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator()
{ return GetEnumerator(); }
}
public class TestConvertFromType2 : IEnumerable<object[]>
{
private readonly List<object[]> _data = new List<object[]>
{
new object[] { "Latitude:12.345;Longitude:342.12",
new GeoPoint {
Latitude = 12.345, Longitude = 342.12
}},
new object[] { "Latitude:11.234;Longitude:345.12",
new GeoPoint {
Latitude = 11.234, Longitude = 345.12
}}
};
public IEnumerator<object[]> GetEnumerator()
{ return _data.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator()
{ return GetEnumerator(); }
}
Generic Converter
public class EmployeeFilterTypeConverter<T> : TypeConverter where T: new()
/*
1. You can't declare T type = new T() without this constraint
Evidently it is because compiler can't say what is the type!
https://stackoverflow.com/a/29345294
*/
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (typeof(string) == sourceType)
return true;
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var strVal = value as String;
if (string.IsNullOrEmpty(strVal))
return new EmployeeFilter();
T converTo = new T();
string[] filters = strVal.Split(';');
foreach (var filter in filters)
{
string[] filterSplit = filter.Split(':');
if (filterSplit.Length == 2)
{
string key = filterSplit[0];
string val = filterSplit[1];
SetPropertyValue(converTo, key, val);
}
}
return converTo;
}
private void SetPropertyValue(T converTo, string key, string val)
{
Type t = typeof(T);
PropertyInfo[] props = t.GetProperties(BindingFlags.Instance | BindingFlags.Public);
PropertyInfo prop = props.Where(p => p.Name.Equals(key, StringComparison.CurrentCultureIgnoreCase) == true && p.CanWrite).FirstOrDefault();
if (prop is null) return;
prop.SetValue(converTo, TypeDescriptor.GetConverter(prop.PropertyType).ConvertFrom(val));
/*
1. Problem: val is a string and if your target property is non-string there is
a contradiction.
The following link offers a solution
https://stackoverflow.com/a/2380483
*/
}
}
EmployeeFilter
[TypeConverter(typeof(EmployeeFilterTypeConverter<EmployeeFilter>))]
public class EmployeeFilter: IEquatable<EmployeeFilter>
{
/*
1. As you can see, the DTO omitted the
a. ID
b. DOB
*/
public string FirstName { get; set; }
public string LastName { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public DateTime? DOJ { get; set; }
public bool Equals(EmployeeFilter other)
{
/*
1. You need to put parenthesis around (this.FirstName == other.FirstName)
2. https://learn.microsoft.com/en-us/dotnet/api/system.iequatable-1?view=net-5.0
*/
return (this.FirstName == other.FirstName) &&
(this.LastName == other.LastName) &&
(this.Street == other.Street) &&
(this.City == other.City) &&
(this.State == other.State) &&
(this.ZipCode == other.ZipCode) &&
(this.DOJ == other.DOJ);
}
}
GeoPoint
public class GeoPoint: IEquatable<GeoPoint>
{
public double Latitude { get; set; }
public double Longitude { get; set; }
public static bool TryParse(string s, out GeoPoint result)
{
result = null;
var parts = s.Split(',');
if (parts.Length != 2)
{
return false;
}
double latitude, longitude;
if (double.TryParse(parts[0], out latitude) &&
double.TryParse(parts[1], out longitude))
{
result = new GeoPoint() { Longitude = longitude, Latitude = latitude };
return true;
}
return false;
}
public bool Equals(GeoPoint other)
{
return (this.Latitude == other.Latitude) && (this.Longitude == other.Longitude);
}
Edit: Added model classes
Consider the following classes:
public class Potato
{
[Key]
public string Farm { get; set; }
[Key]
public int Size { get; set; }
public string Trademark { get; set; }
}
public class Haybell
{
[Key]
public string color { get; set; }
public int StrawCount { get; set; }
}
public class Frog
{
[Key]
public bool IsAlive { get; set; }
[Key]
public bool IsVirulent { get; set; }
public byte LimbCount { get; set; } = 4;
public ConsoleColor Color { get; set; }
}
Each class has properties with [Key] attribute. Is it possible to dynamically group an IEnumerable of any of these classes by their respective [Key] attributes?
I would go for adding extension methods for each your types, like
Option 1:
static class Extensions
{
public static IEnumerable<IGrouping<Tuple<string, int>, Potato>>
GroupByPrimaryKey(this IEnumerable<Potato> e)
{
return e.GroupBy(p => Tuple.Create(p.Farm, p.Size));
}
public static IEnumerable<IGrouping<Tuple<bool, bool>, Frog>>
GroupByPrimaryKey(this IEnumerable<Frog> e)
{
return e.GroupBy(p => Tuple.Create(p.IsAlive, p.IsVirulent));
}
}
If there are lots of types, you may generate the code using t4.
Usage: .GroupByPrimaryKey().
Option 2:
A simpler variation:
static class Extensions
{
public static Tuple<string, int> GetPrimaryKey(this Potato p)
{
return Tuple.Create(p.Farm, p.Size);
}
public static Tuple<bool, bool> GetPrimaryKey(this Frog p)
{
return Tuple.Create(p.IsAlive, p.IsVirulent);
}
}
Usage: .GroupBy(p => p.GetPrimaryKey()).
Option 3:
A solution with reflection is possible, but will be slow. Sketch (far from production-ready!)
class CombinedKey : IEquatable<CombinedKey>
{
object[] _keys;
CombinedKey(object[] keys)
{
_keys = keys;
}
public bool Equals(CombinedKey other)
{
return _keys.SequenceEqual(other._keys);
}
public override bool Equals(object obj)
{
return obj is CombinedKey && Equals((CombinedKey)obj);
}
public override int GetHashCode()
{
return 0;
}
public static CombinedKey GetKey<T>(T instance)
{
return new CombinedKey(GetKeyAttributes(typeof(T)).Select(p => p.GetValue(instance, null)).ToArray());
}
private static PropertyInfo[] GetKeyAttributes(Type type)
{
// you definitely want to cache this
return type.GetProperties()
.Where(p => Attribute.GetCustomAttribute(p, typeof(KeyAttribute)) != null)
.ToArray();
}
}
Usage: GroupBy(p => CombinedKey.GetKey(p))
The challenge here is that you need to build an anonymous type in order to have a GroupBy Expression that can translate to SQL or any other LINQ provider.
I'm not sure that you can do that using reflection (not without some really complex code to create an anonymous type at runtime). But you could create the grouping expression if you were willing to provide an example of the anonymous type as the seed.
public static Expression<Func<TSource, TAnon>> GetAnonymous<TSource,TAnon>(TSource dummy, TAnon example)
{
var ctor = typeof(TAnon).GetConstructors().First();
var paramExpr = Expression.Parameter(typeof(TSource));
return Expression.Lambda<Func<TSource, TAnon>>
(
Expression.New
(
ctor,
ctor.GetParameters().Select
(
(x, i) => Expression.Convert
(
Expression.Property(paramExpr, x.Name), // fetch same named property
x.ParameterType
)
)
), paramExpr);
}
And here's how you would use it (Note: the dummy anonymous type passed to the method is there in order to make the anonymous type a compile-time Type, the method doesn't care what the values are that you pass in for it.) :
static void Main()
{
var groupByExpression = GetAnonymous(new Frog(), new {IsAlive = true, IsVirulent = true});
Console.WriteLine(groupByExpression);
var frogs = new []{ new Frog{ IsAlive = true, IsVirulent = false}, new Frog{ IsAlive = false, IsVirulent = true}, new Frog{ IsAlive = true, IsVirulent = true}};
var grouped = frogs.AsQueryable().GroupBy(groupByExpression);
foreach (var group in grouped)
{
Console.WriteLine(group.Key);
}
}
Which produces:
Param_0 => new <>f__AnonymousType0`2(Convert(Param_0.IsAlive, Boolean), Convert(Param_0.IsVirulent, Boolean))
{ IsAlive = True, IsVirulent = False }
{ IsAlive = False, IsVirulent = True }
{ IsAlive = True, IsVirulent = True }
Somebody had posted a valid answer and removed it later for some reason. Here it is:
Combined key class:
class CombinedKey<T> : IEquatable<CombinedKey<T>>
{
readonly object[] _keys;
public bool Equals(CombinedKey<T> other)
{
return _keys.SequenceEqual(other._keys);
}
public override bool Equals(object obj)
{
return obj is CombinedKey<T> key && Equals(key);
}
public override int GetHashCode()
{
int hash = _keys.Length;
foreach (object o in _keys)
{
if (o != null)
{
hash = hash * 13 + o.GetHashCode();
}
}
return hash;
}
readonly Lazy<Func<T, object[]>> lambdaFunc = new Lazy<Func<T, object[]>>(() =>
{
Type type = typeof(T);
var paramExpr = Expression.Parameter(type);
var arrayExpr = Expression.NewArrayInit(
typeof(object),
type.GetProperties()
.Where(p => (Attribute.GetCustomAttribute(p, typeof(KeyAttribute)) != null))
.Select(p => Expression.Convert(Expression.Property(paramExpr, p), typeof(object)))
.ToArray()
);
return Expression.Lambda<Func<T, object[]>>(arrayExpr, paramExpr).Compile();
}, System.Threading.LazyThreadSafetyMode.PublicationOnly);
public CombinedKey(T instance)
{
_keys = lambdaFunc.Value(instance);
}
}
Caller function and the actual usage:
public static class MyClassWithLogic
{
//Caller to CombinedKey class
private static CombinedKey<Q> NewCombinedKey<Q>(Q instance)
{
return new CombinedKey<Q>(instance);
}
//Extension method for IEnumerables
public static IEnumerable<T> DistinctByPrimaryKey<T>(this IEnumerable<T> entries) where T : class
{
return entries.AsQueryable().GroupBy(NewCombinedKey)
.Select(r => r.First());
}
}
Yes, it is relatively slow, so if it is a problem, then Klaus Gütter's solutions are the way to go.
I'm attempting to create a mapper so PetaPoco can hydrate and persist POCOs with Enumeration class properties. See more about Enumeration classes here or here.
For instance, Take this class.
public class PetType : Headspring.Enumeration<PetType>
{
public static readonly PetType Frog = new PetType(1, "Frog");
public static readonly PetType Cat = new PetType(2, "Cat");
public static readonly PetType Fish = new PetType(3, "Fish");
public static readonly PetType Dog = new PetType(4, "Dog");
private PetType(int value, string displayName) : base(value, displayName) { }
}
Which can be used like so:
var MyPet = PetType.Dog;
Here is the Poco I want to hydrate/persist with the database:
public class Pet
{
public int ID { get; set; }
public string OwnerName { get; set; }
public DateTime DateOfBirth { get; set; }
public string PetName{ get; set; }
public PetType PetType{ get; set; }
}
I have designed a custom mapper that will work with PetType:
class EnumClassMapper : PetaPoco.StandardMapper
{
public override Func<object, object> GetFromDbConverter(System.Reflection.PropertyInfo targetProperty, Type sourceType)
{
if (targetProperty.PropertyType == typeof(PetType))
{
return (x) => PetType.FromValue((int) x);
}
return base.GetFromDbConverter(targetProperty, sourceType);
}
public override Func<object, object> GetToDbConverter(System.Reflection.PropertyInfo sourceProperty)
{
if (sourceProperty.PropertyType == typeof(PetType))
{
return (x) => ((PetType)x).Value;
}
return base.GetToDbConverter(sourceProperty);
}
}
However suppose I create another Enumeration subclass for disposition.
public class Disposition: Headspring.Enumeration<Disposition>
{
public static readonly Friendly = new Disposition(1, "Friendly");
public static readonly Timid = new Disposition(2, "Timid");
public static readonly Aggressive = new Disposition(3, "Aggressive");
private Disposition(int value, string displayName) : base(value, displayName) { }
}
I don't want to have to update my mapper every time I create a new subclass of the Enumeration class. I prefer that the mapping code could recognize that the property type is a descendent of the Enumeration class, and map accordingly. I assume the answer is to make use of reflection, but I don't know how to proceed.
What about
public class EnumClassMapper<T> : PetaPoco.StandardMapper
where T : Headspring.Enumeration<T>
{
public override Func<object, object> GetFromDbConverter(System.Reflection.PropertyInfo targetProperty, Type sourceType)
{
return (x) => Enumeration<T, int>.FromValue((int) x);
}
public override Func<object, object> GetToDbConverter(System.Reflection.PropertyInfo sourceProperty)
{
return (x) => ((T)x).Value;
}
}
var builder = DatabaseConfiguration.Build()
.UsingConnectionStringName("sqlite")
.UsingDefaultMapper<ConventionMapper>(m =>
{
m.FromDbConverter = (targetProperty, sourceType) =>
{
if (targetProperty == null)
return null;
var t = targetProperty.PropertyType;
if (t.BaseType == null || ! t.BaseType.IsGenericType)
return null;
if (t.BaseType.GetGenericTypeDefinition() != typeof(Headspring.Enumeration<>))
return null;
return ((IMapper)Activator.CreateInstance(typeof(EnumClassMapper<>).MakeGenericType(t))).GetFromDbConverter(targetProperty, sourceType);
};
m.ToDbConverter = sourceProperty =>
{
if (sourceProperty == null)
return null;
var t = sourceProperty.PropertyType;
if (t.BaseType == null || !t.BaseType.IsGenericType)
return null;
if (t.BaseType.GetGenericTypeDefinition() != typeof(Headspring.Enumeration<>))
return null;
return ((IMapper)Activator.CreateInstance(typeof(EnumClassMapper<>).MakeGenericType(t))).GetToDbConverter(sourceProperty);
};
});
var db = builder.Create();
I have the following classes domain and Dto classes:
public class Profile
{
public string Name { get; set; }
public string SchoolGrade { get; set; }
}
public class ProfileDTO
{
public string Name { get; set; }
public SchoolGradeDTO SchoolGrade { get; set; }
}
public enum SchoolGradeDTO
{
[Display(Name = "Level One"]
LevelOne,
[Display(Name = "Level Two"]
LevelTwo,
}
I used the following method:
Mapper.CreateMap<Profile, ProfileDTO>()
.ForMember(d => d.SchoolGrade , op => op.MapFrom(o => o.SchoolGrade))
Afterwards, I get the following error:
Requested value 'Level Two' was not found.
How do I map it correctly?
Since you're mapping from the display name and not the enum name you'll need to build a custom mapping function to scan the attributes to find the enum with that display name. You can use ResolveUsing instead of MapFrom to use a custom mapping function:
Mapper.CreateMap<Profile, ProfileDTO>()
.ForMember(d => d.SchoolGrade,
op => op.ResolveUsing(o=> MapGrade(o.SchoolGrade)));
public static SchoolGradeDTO MapGrade(string grade)
{
//TODO: function to map a string to a SchoolGradeDTO
}
You could cache the names in a static dictionary so you don't use reflection every time.
A few methods of doing that can be found here.
Expanding on D Stanley's answer from above in a little more detail, and modified the EnumHelper class from this other discussion to focus on your specific situation as this question really spans two areas, AutoMapper and correctly obtaining an Enum's value from a string.
Enhancing D Stanley's original answer:
public static class QuestionAutoMapperConfig
{
public static void ConfigureAutoMapper()
{
Mapper.CreateMap<Profile, ProfileDTO>()
.ForMember(d => d.SchoolGrade,
op => op.ResolveUsing(o => MapGrade(o.SchoolGrade)));
}
public static SchoolGradeDTO MapGrade(string grade)
{
//TODO: function to map a string to a SchoolGradeDTO
return EnumHelper<SchoolGradeDTO>.Parse(grade);
}
}
I have adjusted the EnumHelper from the mentioned example to quickly show an option where by you could modify the Parse method to first try the standard Enum.Parse(), and failing that to try to do a more detailed comparison of the Enum type by creating a dictionary of the values based either on the enum value name, or it's Display attribute text (if used).
public static class EnumHelper<T>
{
public static IDictionary<string, T> GetValues(bool ignoreCase)
{
var enumValues = new Dictionary<string, T>();
foreach (FieldInfo fi in typeof(T).GetFields(BindingFlags.Static | BindingFlags.Public))
{
string key = fi.Name;
var display = fi.GetCustomAttributes(typeof(DisplayAttribute), false) as DisplayAttribute[];
if (display != null)
key = (display.Length > 0) ? display[0].Name : fi.Name;
if (ignoreCase)
key = key.ToLower();
if (!enumValues.ContainsKey(key))
enumValues[key] = (T)fi.GetRawConstantValue();
}
return enumValues;
}
public static T Parse(string value)
{
T result;
try
{
result = (T)Enum.Parse(typeof(T), value, true);
}
catch (Exception)
{
result = ParseDisplayValues(value, true);
}
return result;
}
private static T ParseDisplayValues(string value, bool ignoreCase)
{
IDictionary<string, T> values = GetValues(ignoreCase);
string key = null;
if (ignoreCase)
key = value.ToLower();
else
key = value;
if (values.ContainsKey(key))
return values[key];
throw new ArgumentException(value);
}
}
in mapping configuration
{
CreateMap<string, CUSTOM_ENUM>().ConvertUsing<StringToEnumConverter<CUSTOM_ENUM>>();
}
converters
public class StringToEnumConverter<T> : ITypeConverter<string, T>, ITypeConverter<string, T?> where T : struct
{
public T Convert(ResolutionContext context)
{
T t;
if (Enum.TryParse(source, out t))
{
return t;
}
var source = (string)context.SourceValue;
if (StringToEnumBase<T>.HasDisplayAttribute())
{
var result = StringToEnumBase<T>.Parse(source);
return result;
}
throw new ConverterException();
}
T? ITypeConverter<string, T?>.Convert(ResolutionContext context)
{
var source = (string)context.SourceValue;
if (source == null) return null;
return Convert(context);
}
}
public static class StringToEnumBase<T> where T:struct
{
public static T Parse(string str)
{
var type = typeof (T);
var enumMembers = type.GetMembers(BindingFlags.Public | BindingFlags.Static);
var enumMembersCollection = enumMembers
.Select(enumMember => new
{
enumMember,
attributes = enumMember.GetCustomAttributes(typeof(DisplayAttribute), false)
})
.Select(t1 => new
{
t1, value = ((DisplayAttribute) t1.attributes[0]).Name
})
.Select(t1 => new Tuple<string, string>(t1.value, t1.t1.enumMember.Name))
.ToList();
var currentMember = enumMembersCollection.FirstOrDefault(item => item.Item1 == str);
if (currentMember == null) throw new ConverterException();
T t;
if (Enum.TryParse(currentMember.Item2, out t))
{
return t;
}
throw new ConverterException();
}
public static bool HasDisplayAttribute()
{
var type = typeof (T);
var attributes = type.GetCustomAttributes(typeof(DisplayAttribute), false);
return attributes.Length > 0;
}
}
I'm trying to control the depth of generation of an object tree with Autofixture. In some cases I want just to generate the root object and in another set of cases I may want to generate the tree up to a certain depth (2, 3, let's say).
class Foo {
public string Name {get;set;}
public Bar Bar {get;set;}
public AnotherType Xpto {get;set;}
public YetAnotherType Xpto {get;set;}
}
class Bar {
public string Name {get;set;}
public string Description {get;set;}
public AnotherType Xpto {get;set;}
public YetAnotherType Xpto {get;set;}
public Xpto Xpto {get;set;}
}
class Xpto {
public string Description {get;set;}
public AnotherType Xpto {get;set;}
public YetAnotherType Xpto {get;set;}
}
With the example above I would want (depth 1) to control the generation process so that only the Foo class is instantiated and the Bar property or any other reference type on that class is not populated or (depth 2) I would want the Foo class instantiated, the Bar property populated with a new instance of Bar but the Xpto property or any other reference type on that class not populated.
In case I did not spot it in the codebase does Autofixture have a customisation or behaviour to allow us to have that kind of control?
Again, it's not recursion that I want to control but the depth of population of the object graph.
No Bar
One-off:
var f = fixture.Build<Foo>().Without(f => f.Bar).Create();
Reusable:
fixture.Customize<Foo>(c => c.Without(f => f.Bar));
var f = fixture.Create<Foo>();
No Xpto
One-off:
var f = fixture
.Build<Foo>()
.With(
f => f.Bar,
fixture.Build<Bar>().Without(b => b.Xpto).Create())
.Create();
Reusable:
fixture.Customize<Bar>(c => c.Without(b => b.Xpto));
var f = fixture.Create<Foo>();
You can use the below GenerationDepthBehavior class as follows:
fixture.Behaviors.Add(new GenerationDepthBehavior(2));
public class GenerationDepthBehavior : ISpecimenBuilderTransformation
{
private const int DefaultGenerationDepth = 1;
private readonly int generationDepth;
public GenerationDepthBehavior() : this(DefaultGenerationDepth)
{
}
public GenerationDepthBehavior(int generationDepth)
{
if (generationDepth < 1)
throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0.");
this.generationDepth = generationDepth;
}
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
return new GenerationDepthGuard(builder, new GenerationDepthHandler(), this.generationDepth);
}
}
public interface IGenerationDepthHandler
{
object HandleGenerationDepthLimitRequest(object request, IEnumerable<object> recordedRequests, int depth);
}
public class DepthSeededRequest : SeededRequest
{
public int Depth { get; }
public int MaxDepth { get; set; }
public bool ContinueSeed { get; }
public int GenerationLevel { get; private set; }
public DepthSeededRequest(object request, object seed, int depth) : base(request, seed)
{
Depth = depth;
Type innerRequest = request as Type;
if (innerRequest != null)
{
bool nullable = Nullable.GetUnderlyingType(innerRequest) != null;
ContinueSeed = nullable || innerRequest.IsGenericType;
if (ContinueSeed)
{
GenerationLevel = GetGenerationLevel(innerRequest);
}
}
}
private int GetGenerationLevel(Type innerRequest)
{
int level = 0;
if (Nullable.GetUnderlyingType(innerRequest) != null)
{
level = 1;
}
if (innerRequest.IsGenericType)
{
foreach (Type generic in innerRequest.GetGenericArguments())
{
level++;
level += GetGenerationLevel(generic);
}
}
return level;
}
}
public class GenerationDepthGuard : ISpecimenBuilderNode
{
private readonly ThreadLocal<Stack<DepthSeededRequest>> requestsByThread
= new ThreadLocal<Stack<DepthSeededRequest>>(() => new Stack<DepthSeededRequest>());
private Stack<DepthSeededRequest> GetMonitoredRequestsForCurrentThread() => this.requestsByThread.Value;
public GenerationDepthGuard(ISpecimenBuilder builder)
: this(builder, EqualityComparer<object>.Default)
{
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler)
: this(
builder,
depthHandler,
EqualityComparer<object>.Default,
1)
{
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler,
int generationDepth)
: this(
builder,
depthHandler,
EqualityComparer<object>.Default,
generationDepth)
{
}
public GenerationDepthGuard(ISpecimenBuilder builder, IEqualityComparer comparer)
{
this.Builder = builder ?? throw new ArgumentNullException(nameof(builder));
this.Comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
this.GenerationDepth = 1;
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler,
IEqualityComparer comparer)
: this(
builder,
depthHandler,
comparer,
1)
{
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler,
IEqualityComparer comparer,
int generationDepth)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
if (depthHandler == null) throw new ArgumentNullException(nameof(depthHandler));
if (comparer == null) throw new ArgumentNullException(nameof(comparer));
if (generationDepth < 1)
throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0.");
this.Builder = builder;
this.GenerationDepthHandler = depthHandler;
this.Comparer = comparer;
this.GenerationDepth = generationDepth;
}
public ISpecimenBuilder Builder { get; }
public IGenerationDepthHandler GenerationDepthHandler { get; }
public int GenerationDepth { get; }
public int CurrentDepth { get; }
public IEqualityComparer Comparer { get; }
protected IEnumerable RecordedRequests => this.GetMonitoredRequestsForCurrentThread();
public virtual object HandleGenerationDepthLimitRequest(object request, int currentDepth)
{
return this.GenerationDepthHandler.HandleGenerationDepthLimitRequest(
request,
this.GetMonitoredRequestsForCurrentThread(), currentDepth);
}
public object Create(object request, ISpecimenContext context)
{
if (request is SeededRequest)
{
int currentDepth = 0;
var requestsForCurrentThread = GetMonitoredRequestsForCurrentThread();
if (requestsForCurrentThread.Count > 0)
{
currentDepth = requestsForCurrentThread.Max(x => x.Depth) + 1;
}
DepthSeededRequest depthRequest = new DepthSeededRequest(((SeededRequest)request).Request, ((SeededRequest)request).Seed, currentDepth);
if (depthRequest.Depth >= GenerationDepth)
{
var parentRequest = requestsForCurrentThread.Peek();
depthRequest.MaxDepth = parentRequest.Depth + parentRequest.GenerationLevel;
if (!(parentRequest.ContinueSeed && currentDepth < depthRequest.MaxDepth))
{
return HandleGenerationDepthLimitRequest(request, depthRequest.Depth);
}
}
requestsForCurrentThread.Push(depthRequest);
try
{
return Builder.Create(request, context);
}
finally
{
requestsForCurrentThread.Pop();
}
}
else
{
return Builder.Create(request, context);
}
}
public virtual ISpecimenBuilderNode Compose(
IEnumerable<ISpecimenBuilder> builders)
{
var composedBuilder = ComposeIfMultiple(
builders);
return new GenerationDepthGuard(
composedBuilder,
this.GenerationDepthHandler,
this.Comparer,
this.GenerationDepth);
}
internal static ISpecimenBuilder ComposeIfMultiple(IEnumerable<ISpecimenBuilder> builders)
{
ISpecimenBuilder singleItem = null;
List<ISpecimenBuilder> multipleItems = null;
bool hasItems = false;
using (var enumerator = builders.GetEnumerator())
{
if (enumerator.MoveNext())
{
singleItem = enumerator.Current;
hasItems = true;
while (enumerator.MoveNext())
{
if (multipleItems == null)
{
multipleItems = new List<ISpecimenBuilder> { singleItem };
}
multipleItems.Add(enumerator.Current);
}
}
}
if (!hasItems)
{
return new CompositeSpecimenBuilder();
}
if (multipleItems == null)
{
return singleItem;
}
return new CompositeSpecimenBuilder(multipleItems);
}
public virtual IEnumerator<ISpecimenBuilder> GetEnumerator()
{
yield return this.Builder;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
public class GenerationDepthHandler : IGenerationDepthHandler
{
public object HandleGenerationDepthLimitRequest(
object request,
IEnumerable<object> recordedRequests, int depth)
{
return new OmitSpecimen();
}
}
This feature was requested in a github issue. It was ultimately rejected. However, it was rejected because there was a nice, simple solution posted within the issue.
public class GenerationDepthBehavior: ISpecimenBuilderTransformation
{
public int Depth { get; }
public GenerationDepthBehavior(int depth)
{
Depth = depth;
}
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
return new RecursionGuard(builder, new OmitOnRecursionHandler(), new IsSeededRequestComparer(), Depth);
}
private class IsSeededRequestComparer : IEqualityComparer
{
bool IEqualityComparer.Equals(object x, object y)
{
return x is SeededRequest && y is SeededRequest;
}
int IEqualityComparer.GetHashCode(object obj)
{
return obj is SeededRequest ? 0 : EqualityComparer<object>.Default.GetHashCode(obj);
}
}
}
You can then use this as follows:
fixture.Behaviors.Add(new GenerationDepthBehavior(2));