I have an immutable DTO that I'd like to fake with Bogus Faker (version 31.0.2) but the property with an overriding rule only returns what it was initialized with by the constructor:
Example DTO (real ones are more complex)
using Xunit;
using Bogus;
namespace SO.Tests
{
class ClassWithInitialization
{
public ClassWithInitialization(string name)
{
this.Name = name
}
public string Name { get; }
}
Example DTO Faker
class FakeClassWithInitialization : Faker<ClassWithInitialization>
{
private FakeClassWithInitialization() { }
public static CreateDefault()
{
return (FakeClassWithInitialization) new FakeClassWithInitialization()
.CustomInstantiator(f => new ClassWithInitialization(null))
.RuleFor(o => o.Name, f => f.Person.FullName);
}
public FakeClassWithInitialization WithName(string name)
{
RuleFor(o => o.Name, f => name);
return this;
}
}
Example Tests
Both of the following tests fail as the Name property remains null as provided in the constructor.
public class Tests
{
[Fact]
public void TestClassWithInitialization()
{
var faker = FakeClassWithInitialization
.CreateDefault();
var testPoco = faker.Generate();
Assert.False(string.IsNullOrEmpty(testPoco.Name)); #fails
}
[Fact]
public void TestClassWithInitialization_with_overriding_rule()
{
var faker = FakeClassWithInitialization
.CreateDefault()
.WithName("John Smith");
var testPoco = faker.Generate();
Assert.AreEqual("John Smith", testPoco.Name); #fails
}
}
}
Although I could use Faker to generate random data for the constructor I would like to be able to use this fake instance to generate alternative versions, for instance, with a fixed Name as exampled by the second test above.
Why is this not working and are there any known workarounds?
Note: this is not the same as the question How can I use Bogus with private setters
It is possible but I'd advise against it because the solution relies on .NET's Reflection.
There's a new Faker<T>(binder:...) binder constructor parameter. The IBinder interface is what Faker<T> uses to reflect over T to discover properties and fields that are settable. Whatever IBinder.GetMembers(Type t) returns is what Faker<> sees in T.
With this information, let's look at how the compiler generates an object with a public parameterized constructor and read-only property:
public class Foo
{
public Foo(string name){
this.Name = name;
}
public string Name { get; }
}
The C# compiler generates:
public class Foo
{
// Fields
[CompilerGenerated, DebuggerBrowsable((DebuggerBrowsableState) DebuggerBrowsableState.Never)]
private readonly string <Name>k__BackingField;
// Methods
public Foo(string name)
{
this.<Name>k__BackingField = name;
}
// Properties
public string Name => this.<Name>k__BackingField;
}
The storage for the Foo.Name property uses a backing field called Foo.<Name>k__BackingField. This backing field is what we need IBinder to hoist into Faker<>. The following BackingFieldBinder : IBinder does this:
public class BackingFieldBinder : IBinder
{
public Dictionary<string, MemberInfo> GetMembers(Type t)
{
var availableFieldsForFakerT = new Dictionary<string, MemberInfo>();
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
var allMembers = t.GetMembers(bindingFlags);
var allBackingFields = allMembers
.OfType<FieldInfo>()
.Where(fi => fi.IsPrivate && fi.IsInitOnly)
.Where(fi => fi.Name.EndsWith("__BackingField"))
.ToList();
foreach( var backingField in allBackingFields){
var fieldName = backingField.Name.Substring(1).Replace(">k__BackingField","");
availableFieldsForFakerT.Add(fieldName, backingField);
}
return availableFieldsForFakerT;
}
}
Customize the GetMembers() method above to suit your needs. You'll need to change the code if you want to include public fields or properties of T too.
The last problem we have to solve is creating an object without specifying constructor arguments. We can do this by using .GetUninitializedObject() from FormatterServices or RuntimeHelpers. To do this, we'll create an extension method that extends the Faker<T> API as shown below:
public static class MyExtensionsForFakerT
{
public static Faker<T> SkipConstructor<T>(this Faker<T> fakerOfT) where T : class
{
return fakerOfT.CustomInstantiator( _ => FormatterServices.GetUninitializedObject(typeof(T)) as T);
}
}
With these two components in place, we can finally write the following code:
void Main()
{
var backingFieldBinder = new BackingFieldBinder();
var fooFaker = new Faker<Foo>(binder: backingFieldBinder)
.SkipConstructor()
.RuleFor(f => f.Name, f => f.Name.FullName());
var foo = fooFaker.Generate();
foo.Dump();
}
public class Foo
{
public Foo(string name)
{
this.Name = name;
}
public string Name {get;}
}
You can find a full working example here. Additionally, you may find other solutions in Issue 213 helpful.
I just tried this and it seems to work :
class FakeClassWithInitialization : Faker<ClassWithInitialization>
{
private FakeClassWithInitialization() { }
public static FakeClassWithInitialization CreateDefault()
{
return (FakeClassWithInitialization) new FakeClassWithInitialization()
.CustomInstantiator(f => new ClassWithInitialization(f.Person.FullName));
}
}
I used directly the class constructor with the generator instead of using the generator with the property.
I also remove the WithName method that was not used.
Edit : Seems I misunderstood the question.
I don't know much about Bogus. I thought you could use optional parameters in "CreateDefault" method but you told DTO was complex so... There will be too much parameters.
I think you can achieve what you want with the builder pattern :
public class Builder
{
private string _name;
public Builder WithName(string name)
{
_name = name;
return this;
}
public ClassWithInitialization Build()
{
return new Faker<ClassWithInitialization>()
.CustomInstantiator(f =>
new ClassWithInitialization(
_name ?? f.Person.FullName
))
.Generate();
}
}
var faker = new Builder().WithName("Hello").Build();
var faker2 = new Builder().Build();
You can delete the FakeClassWithInitialization and replace it with a classic "Builder".
Related
Lets say you have simple chained method but you are trying to access or set a value in a class property (internal/external doesnt matter). Using a Func seems to be working and finds the relation between generic class that is passed and access its properties correctly but i am not sure if its necessary.
Is there a way of setting the method variable cleanly as in Main method below since it is aware of the Generic class association without doing new Props().Property for example?
//sample console app
public class Props {
public string FirstProp = "lets say object";
public string SecondProp = "Pretend some other object";
}
public class Logic<T> where T : class, new()
{
private string outString { get; set; }
public Logic<T> GetPropertyValue(Func<T, object> propertySelector)
{
return this;
}
public Logic<T> GetLambda(Expression<Func<T, object>> propertySelector)
{
var breakpointCheck = propertySelector; //{x => x.SecondProp}
return this;
}
}
class Program
{
static void Main(string[] args)
{
var Test =
new Logic<Props>()
.GetPropertyValue(x => x.FirstProp) //dummy check
.GetLambda(x => x.SecondProp); //passed correctly {x => x.SecondProp}
var HowToGetThis =
new Logic<Props>()
.GetPropertyValue(FirstProp) // or GetPropertyValue(Props.FirstProp)
.GetLambda(x => x.SecondProp);
}
}
Question
How do I define an incoming Type T constraint that will allow me to call a static method on the class (of type T) to get the intended IndexModel object for passing to Mongo?
Background
I'm currently trying to write a Mongo Provider class that will allow me to ensure my particular database and collection are present before doing any operations with them, since there is a potential that the container or server it resides in could be destroyed and recreated at any time, and I'd prefer to have a safe way in code to ensure that the external dependency is there (instance is beyond my control, so I have to trust that something is there).
One of the things I'm trying to do, since I've managed to do what I stated above for Database and Collection instantiation, is to also generate indexes. My idea was to have a static method on the classes that would return their specific definition of an index model. This way, each class would be responsible for their own Mongo indexes, rather than some convoluted switch-case statement in my Provider based on the incoming type of T.
My first idea was to have an interface that shared this method, but Interfaces don't allow you to declare a static method. Similarly, I tried an Abstract Base-class and found that the static implementation would call the base class that defined the method, rather than any overrides in an inheritor.
Sample Code
public class MyClass
{
public DateTime DateValue { get; set; }
public int GroupId { get; set; }
public string DataType { get; set; }
public static IEnumerable<CreateIndexModel<MyClass>> GetIndexModel(IndexKeysDefinitionBuilder<MyClass> builder)
{
yield return new CreateIndexModel<MyClass>(
builder.Combine(
builder.Descending(entry => entry.DateValue),
builder.Ascending(entry => entry.GroupId),
builder.Ascending(entry => entry.DataType)
)
);
}
}
Edit
I guess I should probably include a shell of my Mongo Provider class. See below:
Edit #2 due to questions about how this hasn't solved my problem, I'm updating the MongoProvider to have the problematic code. Note: Once this method is included, the class will no longer compile, since it isn't possible given what I've done thus far.
public class MongoProvider
{
private readonly IMongoClient _client;
private MongoPrivder(ILookup<string, string> lookup, IMongoClient client)
{
_client = client;
foreach(var database in lookup)
foreach(var collection in database)
Initialize(database.Key, collection);
}
public MongoProvider(IConfiguration config) :this(config.GetMongoObjects(), config.GetMongoClient())
{}
public MongoProvider(IConfiguration config, IMongoClient client) : this(config.GetMongoObjects(), client)
{}
private void Initialize(string database, string collection)
{
var db = _client.GetDatabase(database);
if (!db.ListCollectionNames().ToList().Any(name => name.Equals(collection)))
db.CreateCollection(collection);
}
// The Problem
private void InitializeIndex<T>(string database, string collection)
{
IEnumerable<CreateIndexModel<T>> models;
switch (T)
{
case MyClass:
model = MyClass.GetIndexModel();
break;
default:
break;
}
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
}
Edit #3
As a stop-gap, I've gone ahead and done something terrible (not sure if it's going to work yet), and I'll supply the example so you can know my best solution thus far.
public static class Extensions
{
#region Object Methods
public static T TryCallMethod<T>(this object obj, string methodName, params object[] args) where T : class
{
var method = obj.GetType().GetMethod(methodName);
if (method != null)
{
return method.Invoke(obj, args) as T;
}
return default;
}
#endregion
}
This allows me to do the following (inside of MongoProvider)
private async void InitializeIndex<T>(string database, string collection) where T : new()
{
var models = new T().TryCallMethod<IEnumerable<CreateIndexModel<T>>>("GetIndexModel");
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
Since it doesn't look like I'm going to get an answer to this, I figured I would provide my solution for future searches of this question. Basically, I added an extension method to the base object class, and used reflection to determine if the method I was looking for was there. From there, I returned a value of true or false, depending on if the method was found, and output the return value to a parameter, in the traditional TryGet pattern.
Note to Future Readers
I do not recommend this approach. This is just how I solved my problem for accessing a method on a type of T. Ideally, an instance method would be implemented, and a signature defined in a common Interface, but that wasn't going to work for my use case.
My Answer
public static class Extensions
{
#region Object Methods
public static bool TryCallMethod<T>(this object obj, string methodName, out T result, params object[] args) where T : class
{
result = null;
var method = obj.GetType().GetMethod(methodName);
if (method == null)
return false;
result = method.Invoke(obj, args) as T;
return true;
}
#endregion
}
My data class looks like this (obfuscated from actual usage)
[BsonDiscriminator("data")]
public class DataClass
{
#region Private Fields
private const string MongoCollectionName = "Data";
#endregion
#region Public Properties
public string CollectionName => MongoCollectionName;
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("date_value")]
public DateTime DateValue { get; set; }
[BsonElement("group_id")]
public int GroupId { get; set; }
[BsonElement("data_type")]
public string DataType { get; set; }
[BsonElement("summary_count")]
public long SummaryCount { get; set; }
[BsonElement("flagged_count")]
public long FlaggedCount { get; set; }
[BsonElement("error_count")]
public long ErrorCount { get; set; }
#endregion
#region Constructor
public DataClass()
{
}
public DataClass(int groupId, string dataType = null, long summaryCount = 0, long flaggedCount = 0, long errorCount = 0)
{
Id = ObjectId.GenerateNewId();
DateValue = DateTime.UtcNow;
GroupId = groupId;
DocCount = summaryCount;
DataType = dataType ?? "default_name";
FlaggedCount = flaggedCount;
ErrorCount = errorCount;
}
#endregion
#region Public Methods
public static IEnumerable<CreateIndexModel<AuditEntry>> GetIndexModel(IndexKeysDefinitionBuilder<AuditEntry> builder)
{
yield return new CreateIndexModel<AuditEntry>(
builder.Combine(
builder.Descending(entry => entry.DateValue),
builder.Ascending(entry => entry.GroupId),
builder.Ascending(entry => entry.DataType)
)
);
}
#endregion
}
I would then call the method in the following fashion, inside my MongoProvider class. The ellipses are present to identify that more code exists within the class.
public class MongoProvider : IMongoProvider
{
#region Private Fields
private readonly IMongoClient _client;
#endregion
#region Constructor
...
#endregion
#region Private Methods
private void Initialize(string database, string collection)
{
var db = _client.GetDatabase(database);
if (!db.ListCollectionNames().ToList().Any(name => name.Equals(collection)))
db.CreateCollection(collection);
}
private async Task InitializeIndex<T>(string database, string collection) where T : new()
{
if(new T().TryCallMethod<IEnumerable<CreateIndexModel<T>>>("GetIndexModel", out var models, new IndexKeysDefinitionBuilder<T>()))
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
private static void ValidateOptions<T>(ref FindOptions<T, T> options)
{
if(options != null)
return;
options = new FindOptions<T, T>
{
AllowPartialResults = null,
BatchSize = null,
Collation = null,
Comment = "AspNetWebService",
CursorType = CursorType.NonTailable,
MaxAwaitTime = TimeSpan.FromSeconds(10),
MaxTime = TimeSpan.FromSeconds(10),
Modifiers = null,
NoCursorTimeout = false,
OplogReplay = null
};
}
private static FilterDefinition<T> GetFilterDefinition<T>(Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] builders)
{
if(builders.Length == 0)
builders = new Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] {b => b.Empty};
return new FilterDefinitionBuilder<T>()
.And(builders
.Select(b => b(new FilterDefinitionBuilder<T>()))
);
}
#endregion
#region Public Methods
public async Task<IReadOnlyCollection<T>> SelectManyAsync<T>(string database, string collection, FindOptions<T, T> options = null, params Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] builders) where T : new()
{
ValidateOptions(ref options);
await InitializeIndex<T>(database, collection);
var filter = GetFilterDefinition(builders);
var find = await _client.GetDatabase(database)
.GetCollection<T>(collection)
.FindAsync(filter, options);
return await find.ToListAsync();
}
...
#endregion
}
I have a config service which wraps the baked-in assembly settings, but I'd also like to override these on the command line.
Currently this code is working fine:
public interface ISettings
{
string Url { get; }
}
public class OperationalSettings : ISettings
{
public string Url { get { return ServiceSettings.Default.Url; } }
}
public class CommandLineModel
{
public string Url;
}
public class CommandLineSettings : ISettings
{
private readonly CommandLineModel _model;
public CommandLineSettings(string serialisedSettings)
{
_model = JsonConvert.DeserializeObject<CommandLineModel>(serialisedSettings);
}
public string Url { get { return _model.Url; } }
}
public class ConfigService
{
private readonly ISettings _settings;
public ConfigService(ISettings settings)
{
_settings = settings;
}
public ISettings settings { get { return _settings; } }
}
Then the test driver code:
class Program
{
static void Main(string[] args)
{
ISettings opSettings = new OperationalSettings();
var commandLineTest = "{Url:'http://overridenurl.com'}";
ISettings commandSettings = new CommandLineSettings(commandLineTest);
var configService = new ConfigService(opSettings);
var configServiceUsingCmdOpts = new ConfigService(commandSettings);
}
}
So with this I can override settings using the command line string. However, what I don't like is that if I have a new settings, I now need to add this in 4 places:
The interface
The concrete implementation of the settings wrapper (OperationalSettings)
The command line model for deserialisation
The command line settings implementation that wraps the deserialised model.
This seems to suffer from scalability once I add more properties. Is there a more efficient way to achieve this without so many code changes?
You might take a look at DynamicObject
public class CommandLineModelDictionary : DynamicObject
{
// The inner dictionary.
Dictionary<string, object> dictionary
= new Dictionary<string, object>();
// This property returns the number of elements
// in the inner dictionary.
public int Count
{
get
{
return dictionary.Count;
}
}
// If you try to get a value of a property
// not defined in the class, this method is called.
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
string name = binder.Name.ToLower();
// If the property name is found in a dictionary,
// set the result parameter to the property value and return true.
// Otherwise, return false.
return dictionary.TryGetValue(name, out result);
}
// If you try to set a value of a property that is
// not defined in the class, this method is called.
public override bool TrySetMember(
SetMemberBinder binder, object value)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
dictionary[binder.Name.ToLower()] = value;
// You can always add a value to a dictionary,
// so this method always returns true.
return true;
}
}
Otherwise If you have a simple scenario in which you need an object that can only add and remove members at run time but that does not need to define specific operations and does not have static members, use the ExpandoObject class
here how to use
class Program
{
public static dynamic Dyn { get; set; }
static void Main(string[] args)
{
dynamic model= new CommandLineModelDictionary();
model.Prop1 = "Foo";
model.Prop2 = "toto";
Console.WriteLine(model.Prop1);
Console.WriteLine(model.Prop2);
//otherwise you can use
dynamic dynModel = new ExpandoObject();
dynModelop1 = "Test1";
dynModel2 = "Test2";
Console.WriteLine(dynModel.Prop1);
Console.WriteLine(dynModel.Prop2);
}
}
Let us suppose that we have one class which looks like the following:
public class Entity
{
public IList<string> SomeListOfValues { get; set; }
// Other code
}
Now, suppose we want to persist this using EF Core Code First and that we are using a RDMBS like SQL Server.
One possible approach is obviously to create a wraper class Wraper which wraps the string:
public class Wraper
{
public int Id { get; set; }
public string Value { get; set; }
}
And to refactor the class so that it now depends on a list of Wraper objects. In that case EF would generate a table for Entity, a table for Wraper and stablish a "one-to-many" relation: for each entity there is a bunch of wrapers.
Although this works, I don't quite like the approach because we are changing a very simple model because of persistence concerns. Indeed, thinking just about the domain model, and the code, without the persistence, the Wraper class is quite meaningless there.
Is there any other way persist one entity with a list of strings to a RDBMS using EF Core Code First other than creating a wraper class? Of course, in the end the same thing must be done: another table must be created to hold the strings and a "one-to-many" relationship must be in place. I just want to do this with EF Core without needing to code the wraper class in the domain model.
This can be achieved in a much more simple way starting with Entity Framework Core 2.1. EF now supports Value Conversions to specifically address scenarios like this where a property needs to be mapped to a different type for storage.
To persist a collection of strings, you could setup your DbContext in the following way:
protected override void OnModelCreating(ModelBuilder builder)
{
var splitStringConverter = new ValueConverter<IEnumerable<string>, string>(v => string.Join(";", v), v => v.Split(new[] { ';' }));
builder.Entity<Entity>()
.Property(nameof(Entity.SomeListOfValues))
.HasConversion(splitStringConverter);
}
Note that this solution does not litter your business class with DB concerns.
Needless to say that this solution, one would have to make sure that the strings cannot contains the delimiter. But of course, any custom logic could be used to make the conversion (e.g. conversion from/to JSON).
Another interesting fact is that null values are not passed into the conversion routine but rather handled by the framework itself. So one does not need to worry about null checks inside the conversion routine. However, the whole property becomes null if the database contains a NULL value.
What about Value Comparers?
Creating a migration using this converter leads to the following warning:
The property 'Entity.SomeListOfValues' is a collection or enumeration type with a value converter but with no value comparer. Set a value comparer to ensure the collection/enumeration elements are compared correctly.
Setting the correct comparer for the suggested converter depends on the semantics of your list. For example, if you do not care about the order of its elements, you can use the following comparer:
new ValueComparer<IEnumerable<string>>(
(c1, c2) => new HashSet<string>(c1!).SetEquals(new HashSet<string>(c2!)),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToList()
);
Using this comparer, a reordered list with the same elements would not be detected as changed an thus a roundtrip to the database can be avoided. For more information on the topic of Value Comparers, consider the docs.
UPDATE EF CORE 6.0
In order to benefit from Entity Framework Core 6.0 Compiled Models, we can use the generic overload of HasConversion. So the full picture becomes:
builder.Entity<Foo>()
.Property(nameof(Foo.Bar))
.HasConversion<SemicolonSplitStringConverter, SplitStringComparer>();
...
public class SplitStringComparer : ValueComparer<IEnumerable<string>>
{
public SplitStringComparer() : base(
(c1, c2) => new HashSet<string>(c1!).SetEquals(new HashSet<string>(c2!)),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())))
{
}
}
public abstract class SplitStringConverter : ValueConverter<IEnumerable<string>, string>
{
protected SplitStringConverter(char delimiter) : base(
v => string.Join(delimiter.ToString(), v),
v => v.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries))
{
}
}
public class SemicolonSplitStringConverter : SplitStringConverter
{
public SemicolonSplitStringConverter() : base(';')
{
}
}
You could use the ever useful AutoMapper in your repository to achieve this while keeping things neat.
Something like:
MyEntity.cs
public class MyEntity
{
public int Id { get; set; }
public string SerializedListOfStrings { get; set; }
}
MyEntityDto.cs
public class MyEntityDto
{
public int Id { get; set; }
public IList<string> ListOfStrings { get; set; }
}
Set up the AutoMapper mapping configuration in your Startup.cs:
Mapper.Initialize(cfg => cfg.CreateMap<MyEntity, MyEntityDto>()
.ForMember(x => x.ListOfStrings, opt => opt.MapFrom(src => src.SerializedListOfStrings.Split(';'))));
Mapper.Initialize(cfg => cfg.CreateMap<MyEntityDto, MyEntity>()
.ForMember(x => x.SerializedListOfStrings, opt => opt.MapFrom(src => string.Join(";", src.ListOfStrings))));
Finally, use the mapping in MyEntityRepository.cs so that your business logic doesnt have to know or care about how the List is handled for persistence:
public class MyEntityRepository
{
private readonly AppDbContext dbContext;
public MyEntityRepository(AppDbContext context)
{
dbContext = context;
}
public MyEntityDto Create()
{
var newEntity = new MyEntity();
dbContext.MyEntities.Add(newEntity);
var newEntityDto = Mapper.Map<MyEntityDto>(newEntity);
return newEntityDto;
}
public MyEntityDto Find(int id)
{
var myEntity = dbContext.MyEntities.Find(id);
if (myEntity == null)
return null;
var myEntityDto = Mapper.Map<MyEntityDto>(myEntity);
return myEntityDto;
}
public MyEntityDto Save(MyEntityDto myEntityDto)
{
var myEntity = Mapper.Map<MyEntity>(myEntityDto);
dbContext.MyEntities.Save(myEntity);
return Mapper.Map<MyEntityDto>(myEntity);
}
}
You are right, you do not want to litter your domain model with persistence concerns. The truth is, if you use your same model for your domain and persistence, you will not be able to avoid the issue. Especially using Entity Framework.
The solution is, build your domain model without thinking about the database at all. Then build a separate layer which is responsible for the translation. Something along the lines of the 'Repository' pattern.
Of course, now you have twice the work. So it is up to you to find the right balance between keeping your model clean and doing the extra work. Hint: The extra work is worth it in bigger applications.
This might be late, but you can never tell who it might help.
See my solution based on the previous answer
First, you are going to need this reference using System.Collections.ObjectModel;
Then extend the ObservableCollection<T> and add an implicit operator overload for a standard list
public class ListObservableCollection<T> : ObservableCollection<T>
{
public ListObservableCollection() : base()
{
}
public ListObservableCollection(IEnumerable<T> collection) : base(collection)
{
}
public ListObservableCollection(List<T> list) : base(list)
{
}
public static implicit operator ListObservableCollection<T>(List<T> val)
{
return new ListObservableCollection<T>(val);
}
}
Then create an abstract EntityString class (This is where the good stuff happens)
public abstract class EntityString
{
[NotMapped]
Dictionary<string, ListObservableCollection<string>> loc = new Dictionary<string, ListObservableCollection<string>>();
protected ListObservableCollection<string> Getter(ref string backingFeild, [CallerMemberName] string propertyName = null)
{
var file = backingFeild;
if ((!loc.ContainsKey(propertyName)) && (!string.IsNullOrEmpty(file)))
{
loc[propertyName] = GetValue(file);
loc[propertyName].CollectionChanged += (a, e) => SetValue(file, loc[propertyName]);
}
return loc[propertyName];
}
protected void Setter(ref string backingFeild, ref ListObservableCollection<string> value, [CallerMemberName] string propertyName = null)
{
var file = backingFeild;
loc[propertyName] = value;
SetValue(file, value);
loc[propertyName].CollectionChanged += (a, e) => SetValue(file, loc[propertyName]);
}
private List<string> GetValue(string data)
{
if (string.IsNullOrEmpty(data)) return new List<string>();
return data.Split(';').ToList();
}
private string SetValue(string backingStore, ICollection<string> value)
{
return string.Join(";", value);
}
}
Then use it like so
public class Categorey : EntityString
{
public string Id { get; set; }
public string Name { get; set; }
private string descriptions = string.Empty;
public ListObservableCollection<string> AllowedDescriptions
{
get
{
return Getter(ref descriptions);
}
set
{
Setter(ref descriptions, ref value);
}
}
public DateTime Date { get; set; }
}
Extending on the already accepted answer of adding a ValueConverter within the OnModelCreating; you can have this map out for all entities rather than just explicit ones, and you can support storing delimiting characters:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entity in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entity.ClrType.GetProperties())
{
if (property.PropertyType == typeof(List<string>))
{
modelBuilder.Entity(entity.Name)
.Property(property.Name)
.HasConversion(new ValueConverter<List<string>, string>(v => JsonConvert.SerializeObject(v), v => JsonConvert.DeserializeObject<List<string>>(v)));
}
}
}
}
So the end result is a serialized array of strings in the database. This approach can also work on other serializable types as well (Dictionary<string, string>, simple DTO or POCO objects...
There is a purist deep down somewhere in me that is mad about persisting seralized data into a database, but I have grown to ignore it every once and a while.
I implemented a possible solution by creating a new StringBackedList class, where the actual list content is backed by a string. It works by updating the backing string whenever the list is modified, using Newtonsoft.Json as the serializer (because I already use that in my project, but any would work).
You use the list like this:
public class Entity
{
// that's what stored in the DB, and shouldn't be accessed directly
public string SomeListOfValuesStr { get; set; }
[NotMapped]
public StringBackedList<string> SomeListOfValues
{
get
{
// this can't be created in the ctor, because the DB isn't read yet
if (_someListOfValues == null)
{
// the backing property is passed 'by reference'
_someListOfValues = new StringBackedList<string>(() => this.SomeListOfValuesStr);
}
return _someListOfValues;
}
}
private StringBackedList<string> _someListOfValues;
}
Here's the implementation of the StringBackedList class. For ease of use, the backing property is passed by reference, using this solution.
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
namespace Model
{
public class StringBackedList<T> : IList<T>
{
private readonly Accessor<string> _backingStringAccessor;
private readonly IList<T> _backingList;
public StringBackedList(Expression<Func<string>> expr)
{
_backingStringAccessor = new Accessor<string>(expr);
var initialValue = _backingStringAccessor.Get();
if (initialValue == null)
_backingList = new List<T>();
else
_backingList = JsonConvert.DeserializeObject<IList<T>>(initialValue);
}
public T this[int index] {
get => _backingList[index];
set { _backingList[index] = value; Store(); }
}
public int Count => _backingList.Count;
public bool IsReadOnly => _backingList.IsReadOnly;
public void Add(T item)
{
_backingList.Add(item);
Store();
}
public void Clear()
{
_backingList.Clear();
Store();
}
public bool Contains(T item)
{
return _backingList.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_backingList.CopyTo(array, arrayIndex);
}
public IEnumerator<T> GetEnumerator()
{
return _backingList.GetEnumerator();
}
public int IndexOf(T item)
{
return _backingList.IndexOf(item);
}
public void Insert(int index, T item)
{
_backingList.Insert(index, item);
Store();
}
public bool Remove(T item)
{
var res = _backingList.Remove(item);
if (res)
Store();
return res;
}
public void RemoveAt(int index)
{
_backingList.RemoveAt(index);
Store();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _backingList.GetEnumerator();
}
public void Store()
{
_backingStringAccessor.Set(JsonConvert.SerializeObject(_backingList));
}
}
// this class comes from https://stackoverflow.com/a/43498938/2698119
public class Accessor<T>
{
private Action<T> Setter;
private Func<T> Getter;
public Accessor(Expression<Func<T>> expr)
{
var memberExpression = (MemberExpression)expr.Body;
var instanceExpression = memberExpression.Expression;
var parameter = Expression.Parameter(typeof(T));
if (memberExpression.Member is PropertyInfo propertyInfo)
{
Setter = Expression.Lambda<Action<T>>(Expression.Call(instanceExpression, propertyInfo.GetSetMethod(), parameter), parameter).Compile();
Getter = Expression.Lambda<Func<T>>(Expression.Call(instanceExpression, propertyInfo.GetGetMethod())).Compile();
}
else if (memberExpression.Member is FieldInfo fieldInfo)
{
Setter = Expression.Lambda<Action<T>>(Expression.Assign(memberExpression, parameter), parameter).Compile();
Getter = Expression.Lambda<Func<T>>(Expression.Field(instanceExpression, fieldInfo)).Compile();
}
}
public void Set(T value) => Setter(value);
public T Get() => Getter();
}
}
Caveats: the backing string is only updated when the list itself is modified. Updating a list element via direct access (e.g. via the list indexer) requires a manual call to the Store() method.
I have found a trick and I think this is a very usefull workaround to solve this kind of the problem:
public class User
{
public long UserId { get; set; }
public string Name { get; set; }
private string _stringArrayCore = string.Empty;
// Warnning: do not use this in Bussines Model
public string StringArrayCore
{
get
{
return _stringArrayCore;
}
set
{
_stringArrayCore = value;
}
}
[NotMapped]
public ICollection<string> StringArray
{
get
{
var splitString = _stringArrayCore.Split(';');
var stringArray = new Collection<string>();
foreach (var s in splitString)
{
stringArray.Add(s);
}
return stringArray;
}
set
{
_stringArrayCore = string.Join(";", value);
}
}
}
How to use:
// Write user
using (var userDbContext = new UserSystemDbContext())
{
var user = new User { Name = "User", StringArray = new Collection<string>() { "Bassam1", "Bassam2" } };
userDbContext.Users.Add(user);
userDbContext.SaveChanges();
}
// Read User
using (var userDbContext = new UserSystemDbContext())
{
var user = userDbContext.Users.ToList().Last();
foreach (var userArray in user.StringArray)
{
Console.WriteLine(userArray);
}
}
in the database
Table Users:
UserId | Name | StringArrayCore
1 | User | Bassam1;Bassam2
I've been working on a library to generate fake data using Faker.NET. The problem I'm having is that I don't know how to access an anonymous method that I'm passing to the constructor of my DataGenerator child classes.
The issue is that in order to create a list of generics I had to create base class DataGenerator but I cannot pull my Func<T> member up because that base class is not generic so no Tavailable. However, my DataGenerator<T> class does expose the Generator property which is my anonymous method but I haven't found a way to access it while iterating my list of data generators.
Any advice will be highly appreciated.
This is what I have so far:
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Guid EmpUid { get; set; }
}
// Define other methods and classes here
public abstract class DataGenerator
{
public abstract int GetWeight(string matchingProperty);
public abstract Type Type { get;}
}
public abstract class DataGenerator<T> : DataGenerator
{
public readonly string[] Tags;
public readonly Func<T> Generator;
protected DataGenerator(Func<T> generator, params string[] tags)
{
Tags = tags;
//How to access this?
Generator = generator;
}
public override int GetWeight(string matchingProperty)
{
int sum = (from tag in Tags
where matchingProperty.ToLowerInvariant().Contains(tag.ToLowerInvariant())
select 1).Sum();
return sum;
}
public override Type Type {
get { return typeof(T); }
}
}
public class StringDataGenerator : DataGenerator<string>
{
public StringDataGenerator(Func<string> generator, params string[] tags) : base(generator, tags)
{
}
}
public class GuidDataGenerator : DataGenerator<Guid>
{
public GuidDataGenerator(Func<Guid> generator, params string[] tags)
: base(generator, tags)
{
}
}
And I'm testing it here:
private static void Main(string[] args)
{
var dataGeneratorList = new List<DataGenerator>
{
new StringDataGenerator(Name.First, "first", "name"),
new StringDataGenerator(Name.Last, "last", "name"),
new GuidDataGenerator(Guid.NewGuid, "uid", "id")
};
var writeProperties = typeof (Employee).GetProperties().Where(p => p.CanWrite);
foreach (var property in writeProperties)
{
foreach (var dataGenerator in dataGeneratorList)
{
if (property.PropertyType == dataGenerator.Type)
{
var weigth = dataGenerator.GetWeight(property.Name);
//How to access generator here???
var testValue = dataGenerator.Generator.Invoke();
}
}
}
}
As you tagged, given your current setup, reflection is probably your only option.
var func = dataGenerator.GetType().GetField("Generator").GetValue(dataGenerator);
var testValue = func.GetType().GetMethod("Invoke").Invoke(func, null);
I'm not sure anyone could call this super nice, and it won't be super fast, but it's probably sufficient for anything you need fake data in, I suppose.
For good measure, here's it in action.
Your question is actually a bit more complicated than it may seem at face-value. A nice way of handling this if you only ever use it in object form is just to add an abstract Generate method to the base, non-generic class:
public abstract object Generate();
Then override it in your generic one:
public override object Generate()
{
return this.Generator();
}
Of course, this return an object, which isn't nice in a generic class. But at least it avoids reflection.
Another solution to avoid this reflection nonsense might be the use of covariance, although that will, unfortunately, break for value types, like Guid.
public interface IDataGenerator<out T>
{
int GetWeight(string matchingProperty);
Type Type { get;}
T Generate();
}
public abstract class DataGenerator<T> : IDataGenerator<T>
{
public readonly string[] Tags;
public readonly Func<T> Generator;
protected DataGenerator(Func<T> generator, params string[] tags)
{
Tags = tags;
//How to access this?
Generator = generator;
}
public T Generate(){
return this.Generator();
}
. . .
}
That then turns into a preferable,
private static void Main(string[] args)
{
var dataGeneratorList = new List<IDataGenerator<object>>
{
new StringDataGenerator(Name.First, "first", "name"),
new StringDataGenerator(Name.Last, "last", "name")
// But this line doesn't work
// new GuidDataGenerator(Guid.NewGuid, "uid", "id")
};
var writeProperties = typeof (Employee).GetProperties().Where(p => p.CanWrite);
foreach (var property in writeProperties)
{
foreach (var dataGenerator in dataGeneratorList)
{
if (property.PropertyType == dataGenerator.Type)
{
var weigth = dataGenerator.GetWeight(property.Name);
var testValue = dataGenerator.Generate();
}
}
}
}