String properties behave differently in a request
See original GitHub issueWhile making my own SpecimenBuilder, I stumbled upon an issue, I don’t know how to fix - basically string properties cannot be cast to PropertyInfo, so I cannot access CustomAttributes and the class they are declared in.
Here is some code I made for this example:
[Table("ItemTable", Schema = "prd")]
public class Item : IEntity
{
[Column(@"Id", Order = 1, TypeName = "bigint")]
[Display(Name = "Item id")]
public int Id { get; set; }
[Column(@"Name", Order = 2, TypeName = "nvarchar")]
[MaxLength(5)]
[StringLength(5)]
[Display(Name = "Item name")]
public string Name { get; set; }
[Column(@"Effect", Order = 3, TypeName = "nvarchar")]
[MaxLength(6)]
[StringLength(6)]
[Display(Name = "Item effect")]
public string Effect { get; set; }
[Column(@"LastUpdated", Order = 4, TypeName = "datetime")]
[Display(Name = "Last Updated")]
public System.DateTime? LastUpdated { get; set; }
}
The first thing I do in my CustomSpecimen, is cast request to PropertyInfo, that works wonderful for all members of this class, except for strings.
public object Create(object request, ISpecimenContext context)
{
var property = request as PropertyInfo;
if (property == null) return new NoSpecimen();
ColumnAttribute column = property.GetCustomAttribute<ColumnAttribute>();
TableAttribute table = property.ReflectedType.GetCustomAttribute<TableAttribute>();
...
}
The above code casts the Id and LastUpdated to properties and retrieves their custom attributes without any issues, however, when it comes to Name and Effect properties, the only request that ever comes in, is either of type string (TypeInfo not PropertyInfo), or it is a Ranged string request.
Then, I thought I will be clever, and I will only take the class itself in the request and resolve the properties in a single call to my CustomSpecimen, like this:
public object Create(object request, ISpecimenContext context)
{
var typeInfo = request as TypeInfo;
if (typeInfo == null) return new NoSpecimen();
if (!typeInfo.ImplementedInterfaces.Contains(typeof(IEntity))) return new NoSpecimen();
// Stores the class name, local offsets and other things for incrementing unique IDs
PrepareForNextDataSet(typeInfo.FullName);
TableAttribute table = typeInfo.GetCustomAttribute<TableAttribute>();
var instance = Activator.CreateInstance(typeInfo.AsType());
foreach (var prop in typeInfo.DeclaredProperties)
{
ColumnAttribute column = prop.GetCustomAttribute<ColumnAttribute>();
// Tries to get info about this column from metadata
UniqueKeyModel uniqueKeyModel = GetUniqueKeyModel(table.Name, column.Name);
object resolved;
// If this column is part of unique keys, then assign a unique id
if (uniqueKeyModel?.UniqueKeyOrder != null)
{
// try assigning unique id, if not possible then resolve from context
resolved = GetValueForProperty(prop) ?? context.Resolve(prop);
}
else
{
// if field is not part of UniqueKeys - then randomize output
resolved = context.Resolve(prop);
}
prop.SetValue(instance, resolved, null);
}
return instance;
}
Now, this approach works perfectly until I start to customize specific values using ICustomization interface or simple calls to Fixture.Build(), like this:
public class ItemCustomization : ICustomization
{
/// <inheritdoc />
public void Customize(IFixture fixture)
{
fixture.Customize<Item>(c =>
c.With(p => p.Name, "example"));
}
}
And final usage:
Fixture fixture = new Fixture();
fixture.Customizations.Add(new CustomSpecimen());
fixture.Customize(new ItemCustomization());
var result = fixture.CreateMany<Item>(5).ToList();
My specimen is no longer working, because customization has overriden specific values and the Item class does not get sent to my CustomSpecimen, only the remaining fields as primitive data types.
Please suggest a better way of doing this - in essence, what I am trying to accomplish is to have a CustomSpecimen that would populate fields for all types of entity framework generated classes. (I cannot use the default builders, because I have custom logic based on attributes).
CustomSpecimen validates Column name and Table name attributes for each property against a list, and if that list contains an entry for this column & table, then it will assign a unique id. Otherwise, it will resolve the property from context.
Then it should check, if any of the fields have been overriden with customization, if yes, then it should use that specific value instead.
Issue Analytics
- State:
- Created 5 years ago
- Comments:8 (4 by maintainers)
Top GitHub Comments
Yes, it does, thank you.
@janissimsons Great, you are welcome 😉 Sorry again for the long response time - hope it was still in time.