using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using AgileObjects.ReadableExpressions;
public static class Consts
public const char VisibleCharRangeBegin = '!';
public const char VisibleCharRangeEnd = '~';
public static class CharSets
public const string Alpha = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
public const string Numeric = @"0123456789";
public const string AlphaNumeric = Alpha + Numeric;
public const string Word = AlphaNumeric + @"_";
public const string NonWhiteSpace = @"!""#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
public const string Printable = @" " + NonWhiteSpace;
public static class RuleSetNames
public const string Default = "Default";
public const string Blank = "Blank";
public const string Reserved = "default";
public static readonly string[] Immutable = new string[]
public static class TypeExtensions
private static PropertyInfo? GetCountProperty(this Type type)
return type?.GetProperty("Count");
private static PropertyInfo? GetLengthProperty(this Type type)
return type?.GetProperty("Length");
private static PropertyInfo? GetCountProperty(this object obj)
return obj?.GetType()?.GetCountProperty();
private static PropertyInfo? GetLengthProperty(this object obj)
return obj?.GetType()?.GetLengthProperty();
private static bool HasCountProperty(this Type type)
return type?.GetCountProperty() != null;
private static bool HasLengthProperty(this Type type)
return type?.GetLengthProperty() != null;
public static bool IsCollection(this Type type)
return type is not null &&
(type.HasCountProperty() || type.HasLengthProperty());
public static bool IsNonStringCollection(this Type type)
&& type != typeof(string)
public static bool IsNonStringCollection(this object obj)
var objType = obj?.GetType();
return objType?.IsNonStringCollection() ?? false;
public static int? GetCount(this object obj)
return (int?)obj?.GetCountProperty()?.GetValue(obj);
public static int? GetLength(this object obj)
return (int?)obj?.GetLengthProperty()?.GetValue(obj);
public static int? GetSize(this object obj)
var count = obj?.GetCount();
var length = obj?.GetLength();
public static bool IsEmpty(this object obj)
var size = obj?.GetSize();
return size is null || size == 0;
public static object? DefaultValue(this object obj)
return ExprHelper.DefaultValue(obj.GetType());
public static class StringBuilderExtensions
private const int DefaultIndentationSize = 2;
public static void AppendIndented(this StringBuilder sb, string? value, int depth = 0, int indentationSize = DefaultIndentationSize)
sb.Indent(depth, indentationSize);
public static void AppendLineIndented(this StringBuilder sb, string? value, int depth = 0, int indentationSize = DefaultIndentationSize)
sb.AppendIndented(value, depth, indentationSize);
sb.Append(Environment.NewLine);
private static void Indent(this StringBuilder sb, int depth = 0, int indentationSize = DefaultIndentationSize)
sb.Append(Indentation(depth, indentationSize));
private static string Indentation(int depth, int indentationSize = DefaultIndentationSize) => new(' ', indentationSize * depth);
public static class ToStringHelper
public static string ToString(object? obj)
var sb = new StringBuilder();
AppendObject(obj, sb, 0);
private static void AppendObject(object? obj, StringBuilder sb, int depth)
sb.AppendLineIndented("null", depth);
else if (obj is BaseEntity entity)
AppendEntity(entity, sb, depth);
else if (obj.IsNonStringCollection())
AppendEnumerableWithLabel(obj, sb, depth);
sb.AppendLineIndented(obj.ToString(), depth);
private static void AppendEntity(BaseEntity entity, StringBuilder sb, int depth)
sb.AppendLineIndented($"{entity.GetType().Name} {{", depth);
foreach (var prop in entity.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
AppendProperty(entity, prop, sb, depth + 1);
sb.AppendLineIndented("}", depth);
private static void AppendProperty(BaseEntity entity, PropertyInfo prop, StringBuilder sb, int depth)
dynamic? propValue = prop.GetValue(entity);
sb.AppendIndented($"{prop.Name}: ", depth);
else if (prop.IsNonStringCollection())
AppendEnumerableWithLabel(propValue, sb, depth, prop.Name);
AppendObject(propValue, sb, depth + 1);
private static void AppendEnumerableWithLabel(object? obj, StringBuilder sb, int depth, string? label = null)
label ??= obj.GetType().Name;
sb.AppendIndented($"{label}: [", depth);
sb.Append(Environment.NewLine);
AppendEnumerable(objValue, sb, depth + 1);
sb.AppendLineIndented("]", depth);
private static void AppendEnumerable(object? obj, StringBuilder sb, int depth)
dynamic? enumerable = obj;
foreach (var item in enumerable)
AppendObject(item, sb, depth + 1);
public abstract class BaseEntity
public Guid Id { get; set; }
public string Name { get; set; } = "";
public override string ToString() => ToStringHelper.ToString(this);
public class Person : BaseEntity
public int Age { get; set; }
public decimal Wealth { get; set; }
public List<Pet> Pets { get; set; } = new();
public class Pet : BaseEntity
public PetType PetType { get; set; }
public static partial class ExprHelper
public static Expression<Func<TFrom, TTo?>> ConvertToExpr<TFrom, TTo>()
var param = Expression.Parameter(typeof(TFrom), "from");
return Expression.Lambda<Func<TFrom, TTo?>>(
Expression.Convert(param, typeof(TTo)),
public static Func<TFrom, TTo?> ConvertTo<TFrom, TTo>()
return ConvertToExpr<TFrom, TTo>().Compile();
public static TTo? ConvertTo<TFrom, TTo>(TFrom fromValue)
return ConvertTo<TFrom, TTo>()(fromValue);
private static readonly Dictionary<Type, object?> DefaultValueCache = new();
public static T? DefaultValue<T>()
if (DefaultValueCache.ContainsKey(tType))
return (T?)DefaultValueCache[tType];
var expr = Expression.Lambda<Func<T?>>(
Expression.Default(tType)
var val = expr.Compile()();
DefaultValueCache[tType] = val;
public static object? DefaultValue(Type type)
if (DefaultValueCache.ContainsKey(type))
return DefaultValueCache[type];
var param = Expression.Parameter(type, "object");
var expr = Expression.Lambda<Func<object?>>(
Expression.Default(type),
var val = expr.Compile()();
DefaultValueCache[type] = val;
public static Expression<Func<Faker, Func<TProperty, TProperty, TProperty>>> GetRandomIntExpr<TProperty>()
var faker = Expression.Parameter(typeof(Faker), "faker");
var randomProperty = typeof(Faker).GetProperty("Random");
var intProperty = typeof(Randomizer).GetMethod("Int") ??
throw new NullReferenceException("intProperty");
var randomPropertyAccess = Expression.MakeMemberAccess(faker, randomProperty!);
var minParam = Expression.Parameter(typeof(int), "min");
var maxParam = Expression.Parameter(typeof(int), "max");
var minParamAsProp = Expression.Parameter(typeof(TProperty), "minProp");
var maxParamAsProp = Expression.Parameter(typeof(TProperty), "maxProp");
var randomIntAccess = Expression.Lambda<Func<int, int, int>>(
Expression.Call(randomPropertyAccess, intProperty, minParam, maxParam),
var convertToIntExpr = ConvertToExpr<TProperty, int>();
var minParamProp = Expression.Invoke(
var maxParamProp = Expression.Invoke(
var convertToPropExpr = ConvertToExpr<int, TProperty>();
var intCall = Expression.Invoke(
var finalCall = Expression.Invoke(
var randomIntAccessByProp = Expression.Lambda<Func<TProperty, TProperty, TProperty>>(
return Expression.Lambda<Func<Faker, Func<TProperty, TProperty, TProperty>>>(
public static Expression<Func<TEntity, TProperty?>> CreateMinMaxRule<TEntity, TProperty>(Faker faker, TProperty min, TProperty max)
where TProperty : IComparable<TProperty>
where TEntity : BaseEntity
var getRandomizer = GetRandomIntExpr<TProperty>();
return (TEntity e) => getRandomizer.Compile()(faker)(min, max);
public static class EntityExtensions
public static IEnumerable<PropertyInfo> GetSeededProperties<TEntity>(this TEntity entity)
where TEntity : BaseEntity
return typeof(TEntity).GetProperties(BindingFlags.Instance | BindingFlags.Public)
var propVal = prop.GetValue(entity);
var propType = prop.PropertyType;
if (propType.IsValueType)
return propVal?.Equals(propVal?.DefaultValue()) ?? false;
else if (propType.IsClass)
if (propType.IsNonStringCollection())
return !(propVal?.IsEmpty() ?? true);
return propVal is not null;
$"Property '{prop.Name}' of type '{typeof(TEntity).Name}' is neither a value type nor a class."
public sealed class DeliberateNull { }
public class RuleSet<TEntity>
where TEntity : BaseEntity
public const string DefaultRuleSetName = "Default";
public const string BlankRuleSetName = "Blank";
private string? _name = null;
get => _name ?? string.Empty;
if (_name is null && value is not null)
private static readonly Faker _faker = new();
public static Faker Faker => _faker;
public bool IsNamed => !string.IsNullOrWhiteSpace(Name);
private static readonly Dictionary<string, PropertyInfo> props;
private static readonly Dictionary<string, Expression<Func<TEntity, object?>>> getters;
public static Expression<Func<TEntity, object?>>? Getter(string propName)
return getters.ContainsKey(propName) ?
private readonly Dictionary<string, Expression<Func<TEntity, object?>>> _rules;
private static RuleSet<TEntity>? defaultRuleSet = null;
public static RuleSet<TEntity> DefaultRuleSet
get => defaultRuleSet ??= new(BlankRuleSet, DefaultRuleSetName);
private set => defaultRuleSet = value;
private static RuleSet<TEntity>? blankRuleSet = null;
public static RuleSet<TEntity> BlankRuleSet
get => blankRuleSet ??= new RuleSet<TEntity>(true) { Name = BlankRuleSetName };
var allProperties = typeof(TEntity).GetProperties(BindingFlags.Public | BindingFlags.Instance);
props = allProperties.ToDictionary(
getters = props.ToDictionary(
prop => GenGetter(prop.Value)
private static Expression<Func<TEntity, object?>> CreateExpr(Expression<Func<object?>> valueCalc)
return Expression.Lambda<Func<TEntity, object?>>(
Expression.Invoke(valueCalc),
Expression.Parameter(typeof(TEntity), "entity")
private static object? CreateBlankValueExpr(Type type)
var blankValueGen = Expression.Lambda<Func<Type, object?>>(
Expression.Default(type),
Expression.Parameter(typeof(Type), "type")
return blankValueGen.Compile()(type);
private static Expression<Func<TEntity, object?>> GetBlankRuleFor(PropertyInfo pi)
var type = pi.PropertyType;
var values = Enum.GetValues(type);
return (TEntity entity) => values.GetValue(_faker.Random.Int(1, values.Length - 1));
if (type.IsNonStringCollection())
return CreateExpr(() => Activator.CreateInstance(type));
Type when type == typeof(string) => CreateExpr(() => _faker.Random.String2(_faker.Random.Int(1, 32), Consts.CharSets.Printable)),
Type when type == typeof(Guid) => CreateExpr(() => Guid.NewGuid()),
Type when type == typeof(DateTime) => CreateExpr(() => DateTime.Now),
Type when type == typeof(TimeOnly) => CreateExpr(() => TimeOnly.FromDateTime(DateTime.Now)),
Type when type == typeof(DateOnly) => CreateExpr(() => DateOnly.FromDateTime(DateTime.Today)),
Type when type == typeof(DateTimeOffset) => CreateExpr(() => DateTimeOffset.UtcNow),
Type when type == typeof(TimeSpan) => CreateExpr(() => TimeSpan.FromHours(DateTime.Now.Hour)),
Type when type == typeof(bool) => CreateExpr(() => _faker.Random.Bool()),
Type when type == typeof(sbyte) => CreateExpr(() => _faker.Random.Byte(byte.MinValue, (byte)sbyte.MaxValue)),
Type when type == typeof(byte) => CreateExpr(() => _faker.Random.Byte(byte.MinValue, byte.MaxValue)),
Type when type == typeof(char) => CreateExpr(() => _faker.Random.Char(Consts.VisibleCharRangeBegin, Consts.VisibleCharRangeEnd)),
Type when type == typeof(short) => CreateExpr(() => _faker.Random.Short(short.MinValue, short.MaxValue)),
Type when type == typeof(int) => CreateExpr(() => _faker.Random.Int(int.MinValue, int.MaxValue)),
Type when type == typeof(long) => CreateExpr(() => _faker.Random.Long(long.MinValue, long.MaxValue)),
Type when type == typeof(ushort) => CreateExpr(() => _faker.Random.UShort(ushort.MinValue, ushort.MaxValue)),
Type when type == typeof(uint) => CreateExpr(() => _faker.Random.UInt(uint.MinValue, uint.MaxValue)),
Type when type == typeof(ulong) => CreateExpr(() => _faker.Random.ULong(ulong.MinValue, ulong.MaxValue)),
Type when type == typeof(decimal) => CreateExpr(() => _faker.Random.Decimal(1m, 10000m)),
Type when type == typeof(float) => CreateExpr(() => _faker.Random.Float(1f, 10000f)),
Type when type == typeof(double) => CreateExpr(() => _faker.Random.Double(1d, 10000d)),
Type when type == typeof(DeliberateNull) => CreateExpr(() => null),
_ => CreateExpr(() => CreateBlankValueExpr(type))
public RuleSet() : this(false) { }
public RuleSet(bool creatingBlank)
foreach (var (propName, prop) in props)
var blankRule = GetBlankRuleFor(prop);
AddRule(propName, blankRule);
_rules = new(DefaultRuleSet._rules);
public RuleSet(RuleSet<TEntity>? otherRuleSet, string name)
if (otherRuleSet is null)
throw new ArgumentNullException(nameof(otherRuleSet));
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
_rules = new(otherRuleSet._rules);
private RuleSet<TEntity> As(string newName)
var newRuleSet = new RuleSet<TEntity>(this, newName);
public RuleSet<TEntity> AddToFakerAs(Faker<TEntity> faker, string newName)
var namedRuleSet = As(newName);
namedRuleSet.AddToFaker(faker);
public RuleSet<TEntity> AddToFakerAsDefault(Faker<TEntity> faker)
DefaultRuleSet = new(this, DefaultRuleSetName);
DefaultRuleSet.AddToFaker(faker);
public void AddRule(string propName, Expression<Func<TEntity, object?>> rule)
public void AddRule<TProperty>(string propName, Expression<Func<TEntity, TProperty?>> rule)
AddRule(propName, CastExprToObject(rule));
public void AddRule(string propName, object? rule)
AddRule(propName, ConvertToConstExpr(rule));
public void AddRule<TProperty>(string propName, TProperty? rule)
AddRule(propName, ConvertToConstExpr(rule));
public void AddRule<TProperty>(Expression<Func<TEntity, TProperty?>> propGetter, Expression<Func<TEntity, object?>> rule)
var propName = GetMemberNameFromGetterExpr(propGetter);
public void AddRule<TProperty>(Expression<Func<TEntity, TProperty?>> propGetter, Expression<Func<TEntity, TProperty?>> rule)
var propName = GetMemberNameFromGetterExpr(propGetter);
var ruleReturningObj = CastExprToObject(rule);
AddRule(propName, ruleReturningObj);
public void AddRule<TProperty>(Expression<Func<TEntity, TProperty?>> propGetter, object? rule)
var propName = GetMemberNameFromGetterExpr(propGetter);
AddRule(propName, ConvertToConstExpr(rule));
public void AddRule<TProperty>(Expression<Func<TEntity, TProperty?>> propGetter, TProperty? rule)
var propName = GetMemberNameFromGetterExpr(propGetter);
AddRule(propName, ConvertToConstExpr(rule));
private void AddToFaker(Faker<TEntity> faker)
faker.RuleSet(Name, GenRuleSet());
private Action<IRuleSet<TEntity>> GenRuleSet()
return (IRuleSet<TEntity> faker) =>
foreach (var (propName, rule) in _rules)
var propGetter = RuleSet<TEntity>.getters[propName];
var ruleForProp = rule.Compile();
faker.RuleFor(propGetter, (faker, entity) => ruleForProp(entity));
public void ResetToDefault()
foreach (var propName in props.Keys)
_rules[propName] = DefaultRuleSet._rules[propName];
foreach (var propName in props.Keys)
_rules[propName] = BlankRuleSet._rules[propName];
private static Expression<Func<TEntity, object?>> GenGetter(PropertyInfo prop)
var param = Expression.Parameter(typeof(TEntity), "entity");
var propExpr = Expression.Property(param, prop);
var propObjExpr = Expression.Convert(propExpr, typeof(object));
var expr = Expression.Lambda<Func<TEntity, object?>>(propObjExpr, param);
private static Expression<Func<TEntity, object?>> CastExprToObject<T>(Expression<Func<TEntity, T?>> originalExpr)
var asObject = Expression.Convert(originalExpr.Body, typeof(object));
var expr = Expression.Lambda<Func<TEntity, object?>>(asObject, originalExpr.Parameters);
private static Expression<Func<TEntity, object?>> ConvertToMemberFuncExpr(Expression<Func<object?>> originalExpr)
var param = Expression.Parameter(typeof(TEntity), "entity");
var expr = Expression.Lambda<Func<TEntity, object?>>(originalExpr.Body, param);
private static Expression<Func<TEntity, T?>> ConvertToMemberFuncExpr<T>(Expression<Func<T?>> originalExpr)
var param = Expression.Parameter(typeof(TEntity), "entity");
var expr = Expression.Lambda<Func<TEntity, T?>>(originalExpr.Body, param);
private static Expression<Func<TEntity, object?>> ConvertToConstExpr(object? constValue)
var constExpr = Expression.Constant(constValue, typeof(object));
var param = Expression.Parameter(typeof(TEntity), "entity");
var expr = Expression.Lambda<Func<TEntity, object?>>(constExpr, param);
private static Expression<Func<TEntity, TProperty?>> ConvertToConstExpr<TProperty>(TProperty? constValue)
var constExpr = Expression.Constant(constValue, typeof(TProperty));
var param = Expression.Parameter(typeof(TEntity), "entity");
var expr = Expression.Lambda<Func<TEntity, TProperty?>>(constExpr, param);
public static string GetMemberNameFromGetterExpr<TProperty>(Expression<Func<TEntity, TProperty?>> getter)
var paramType = getter.Parameters[0].Type;
if (getter.Body is not MemberExpression memberExpr)
throw new InvalidOperationException($"Param {getter.ToReadableString()} is not a member getter.");
var member = paramType.GetMember(memberExpr.Member.Name)[0];
public override string ToString()
var rulesString = _rules.Aggregate(string.Empty, (curr, next) =>
$"{curr}{Environment.NewLine} {next.Key}: {next.Value.ToReadableString()}"
{nameof(RuleSet<TEntity>)} {(IsNamed ? $"\"{Name}\"" : "(Unnamed)")} {{
public interface IEntityFaker<TEntity>
where TEntity : BaseEntity
public IEntityFaker<TEntity> With<TProperty>(Expression<Func<TEntity, TProperty?>> getter, TProperty? rule);
public IEntityFaker<TEntity> With<TProperty>(Expression<Func<TEntity, TProperty?>> getter, Expression<Func<TEntity, TProperty?>> rule);
public IEntityFaker<TEntity> WithMinMax<TProperty>(Expression<Func<TEntity, TProperty>> getter, TProperty min, TProperty max)
where TProperty : struct, IComparable<TProperty>;
public IEntityFaker<TEntity> With(TEntity seed);
public IEntityFaker<TEntity> Using<TOtherEntity>(
IEntityFaker<TOtherEntity> otherFaker,
params string[] ruleSetsToUse
) where TOtherEntity : BaseEntity;
public IEntityFaker<TEntity> Using<TOtherEntity>(
IEntityFaker<TOtherEntity> otherFaker,
params string[] ruleSetsToUse
) where TOtherEntity : BaseEntity;
public IEntityFaker<TEntity> As(string name);
public IEntityFaker<TEntity> AsDefault();
public TEntity Create(params string[] ruleSetsToUse);
public List<TEntity> Create(int numToCreate, params string[] ruleSetsToUse);
public IEntityFaker<TEntity> this[string ruleSetName] { get; }
public IEntityFaker<TEntity> Reset(string ruleSetName = Consts.RuleSetNames.Default);
public IEntityFaker<TEntity> ResetToDefault();
public IEntityFaker<TEntity> ResetToBlank();
public class EntityFaker<TEntity> : IEntityFaker<TEntity>
where TEntity : BaseEntity
private const bool DefaultToRandomized = true;
private Guid Id { get; init; }
private Faker<TEntity> Faker { get; init; }
private RuleSet<TEntity> CurrentRuleSet { get; set; }
private Dictionary<string, RuleSet<TEntity>> RuleSets { get; init; }
private delegate TEntity CreationDelegate(params string[] ruleSetsToUse);
private delegate List<TEntity> CollectionCreationDelegate(int count, params string[] ruleSetsToUse);
: this(DefaultToRandomized)
public EntityFaker(bool randomizedDefaults)
CurrentRuleSet = randomizedDefaults ?
new(RuleSet<TEntity>.DefaultRuleSet, Consts.RuleSetNames.Default) :
public IEntityFaker<TEntity> With<TProperty>(
Expression<Func<TEntity, TProperty?>> getter,
CurrentRuleSet.AddRule(getter, rule);
public IEntityFaker<TEntity> With<TProperty>(
Expression<Func<TEntity, TProperty?>> getter,
Expression<Func<TEntity, TProperty?>> rule
CurrentRuleSet.AddRule(getter, rule);
public IEntityFaker<TEntity> WithMinMax<TProperty>(
Expression<Func<TEntity, TProperty>> getter,
) where TProperty : struct, IComparable<TProperty>
if (min.CompareTo(max) > 0)
throw new ArgumentOutOfRangeException(nameof(min));
CurrentRuleSet.AddRule(getter, ExprHelper.CreateMinMaxRule<TEntity, TProperty>(RuleSet<TEntity>.Faker, min, max));
public IEntityFaker<TEntity> With(TEntity seed)
var seededProperties = seed.GetSeededProperties();
foreach (var prop in seededProperties)
var getter = RuleSet<TEntity>.Getter(prop.Name);
Console.WriteLine($"Could not fetch getter for seeded property '{prop.Name}'.");
CurrentRuleSet.AddRule(getter, prop.GetValue(seed));
public IEntityFaker<TEntity> Using<TOtherEntity>(
IEntityFaker<TOtherEntity> otherFaker,
params string[] ruleSetsToUse
) where TOtherEntity : BaseEntity
var props = GetPropertiesOfType(typeof(TOtherEntity));
if (props is null || !props.Any())
Console.WriteLine($"No properties found for type '{typeof(TEntity).Name}'.");
foreach (var prop in props)
entity => otherFaker.Create(ruleSetsToUse)
public IEntityFaker<TEntity> Using<TOtherEntity>(
IEntityFaker<TOtherEntity> otherFaker,
params string[] ruleSetsToUse
) where TOtherEntity : BaseEntity
var props = GetCollectionPropertiesOfType(typeof(TOtherEntity));
if (props is null || !props.Any())
Console.WriteLine($"No properties found for collections of type '{typeof(TEntity).Name}'.");
foreach (var prop in props)
entity => (object?)otherFaker.Create(count, ruleSetsToUse)
public IEntityFaker<TEntity> As(string name)
ValidateRuleSetName(name);
RuleSets[name] = CurrentRuleSet.AddToFakerAs(Faker, name);
public IEntityFaker<TEntity> AsDefault()
RuleSets[Consts.RuleSetNames.Default] = CurrentRuleSet.AddToFakerAsDefault(Faker);
public TEntity Create(params string[] ruleSetsToUse)
var entity = Faker.Generate(GetRuleSetGroupName(ruleSetsToUse));
public List<TEntity> Create(int numToCreate, params string[] ruleSetsToUse)
throw new ArgumentOutOfRangeException(nameof(numToCreate));
var entities = Faker.Generate(numToCreate, GetRuleSetGroupName(ruleSetsToUse));
public IEntityFaker<TEntity> this[string ruleSetName] => Reset(ruleSetName);
public IEntityFaker<TEntity> Reset(string ruleSetName = Consts.RuleSetNames.Default)
if (ruleSetName == Consts.RuleSetNames.Default)
public IEntityFaker<TEntity> ResetToDefault()
CurrentRuleSet.ResetToDefault();
public IEntityFaker<TEntity> ResetToBlank()
private static string GetRuleSetGroupName(params string[] ruleSets)
var ruleSetsToUse = ruleSets.Prepend(Consts.RuleSetNames.Default);
return string.Join(", ", ruleSetsToUse);
private static IEnumerable<PropertyInfo> GetPropertiesOfType(Type otherType)
return typeof(TEntity).GetProperties().Where(prop =>
&& prop.PropertyType is not null
&& prop.PropertyType == otherType
private static IEnumerable<PropertyInfo> GetCollectionPropertiesOfType(Type otherType)
return typeof(TEntity).GetProperties().Where(prop =>
&& prop.PropertyType is not null
&& prop.PropertyType.IsNonStringCollection()
&& prop.PropertyType.GenericTypeArguments.Length == 1
&& prop.PropertyType.GenericTypeArguments[0] == otherType
private static void ValidateRuleSetName(string ruleSetName)
if (string.IsNullOrWhiteSpace(ruleSetName))
throw new InvalidOperationException("Cannot use null, empty, or whitespace-only string as a RuleSet name.");
case Consts.RuleSetNames.Blank:
case Consts.RuleSetNames.Reserved:
throw new InvalidOperationException($"Cannot use reserved name '{ruleSetName}' as a RuleSet name.");
public override string ToString()
var ruleSetNames = RuleSets.Where(rs => rs.Value.IsNamed).Select(rs => rs.Value.Name);
var ruleSetNamesString = string.Join(", ", ruleSetNames);
{nameof(EntityFaker<TEntity>)} {{
{nameof(CurrentRuleSet)}: {CurrentRuleSet}
{nameof(RuleSets)}: {ruleSetNamesString}
private static readonly string NL = Environment.NewLine;
protected static void Log(BaseEntity? entity, string name = "LOGGED ENTITY")
var entityAsString = $"{NL}=== {name} ================================={NL}";
entityAsString += entity?.ToString() ?? string.Empty;
Console.WriteLine(entityAsString);
public static void Main()
var faker = new EntityFaker<Person>();
faker.WithMinMax(e => e.Age, 1, 99)
var person0 = faker.Create();
Log(person0, "Person 0");
faker.With(e => e.Name, "Jack Black")
var person1 = faker.Create("JackBlack");
Log(person1, "Person 1");
faker.With(e => e.Age, 175)
var person2 = faker.Create();
Log(person2, "Person 2");
.WithMinMax(p => p.Wealth, 2222m, 2233m)
var person3 = faker.Create("SomeGuy");
Log(person3, "Person 3");
var person4 = faker.Create("JackBlack");
Log(person4, "Person 4");
var threePeople = faker.Create(3, "SomeGuy");
foreach (var person in threePeople)
Log(person, $"person {personNumber++}");