Setting nested properties via an Expression - c#

I have the following object:
public class ContactImport {
public long Id {get;set;}
public Contact Contact {get;set;}
public Company Company {get;set;}
public Address Address {get;set;}
}
I want to be able to set the properties on the the nested objects dynamically based on an expression passed in (Expression<Func<T, dynamic>>).
To do this I have the following method, this works fine for setting properties on the top level object (such as Id) but fails when trying to set anything on the nested objects (such as Contact.FirstName)
public static void SetPropertyValue<T, TProp>(this T target, Expression<Func<T, TProp>> member, TProp value) {
var selectorExpr = (MemberExpression)(member.Body is UnaryExpression ? ((UnaryExpression)member.Body).Operand : member.Body);
if (selectorExpr != null) {
var property = selectorExpr.Member as PropertyInfo;
if (property != null) {
property.SetValue(target, value);
}
}
}
It looks like it's trying to set the property on the top level object but can't. I take it this is possible but I'm unsure how to achieve it with what I currently have.
The SetPropertyValue method is invoke like this:
public class ImportCheck<T> {
public int id { get; set; }
public string Name { get; set; }
public Type Type { get; set; }
public bool Required { get; set; }
public int? MinLength { get; set; }
public int? MaxLength { get; set; }
public string Value { get; set; }
public Expression<Func<T, dynamic>> AssociatedProperty { get; set; }
}
T pt = (T)Activator.CreateInstance(typeof(T));
foreach (var m in mapping) {
pt.SetPropertyValue(m.AssociatedProperty, Convert.ChangeType(m.Value, Nullable.GetUnderlyingType(m.Type) ?? m.Type));
}
In the above example T is ContactImport, m.AssociatedProperty is the expression and mapping is a List<ImportCheck<ContactImport>>.
Update
My issue seems to boil down to the fact that the expression being passed into SetPropertyValue has a return type of dynamic. If this is set to int and the property on the nested object is an int then it all works. The problem I have then is that I need to explicitly set the result of the expression to match the type of the target property. This however leaves me with another issue in that I need a list of ImportChecks with the possibility of each Expression<Func<T,dynamic>> having a different return type.

Related

Linq query - "Where" on List<T> Where reflected property Contains text/value

I would like to build a Function where user could search if certain property from list contains value
Let say we will have List, and Company will be defined as a class with properties like :
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public string CompanyAddress1 { get; set; }
public string CompanyPostCode { get; set; }
public string CompanyCity { get; set; }
public string CompanyCounty { get; set; }
}
Now - Ideally I would like to have function with this parameters
List<Company> FilterCompanies(List<Company> unfilteredList, string fieldToQueryOn, string query)
{
// linq version what ideally would like to archeve
return unfilteredList.Where(x => x."fieldToQueryOn".ToString().ToLower().Contains(query.ToLower())).ToList();
}
and call :
var variable = FilterCompanies(NotNullFilledUnfilteredList, "CompanyCity", "New York")
I tried to follow the tutorial at learn.microsoft.com and it's easy, but I don't have clue how to extend that solution with reflection on Type and use it in an expression tree.
You can use Type.GetProperty to find a property by name using reflection, and then use GetValue to retrieve the value:
List<Company> FilterCompanies(List<Company> list, string propertyName, string query)
{
var pi = typeof(Company).GetProperty(propertyName);
query = query.ToLower();
return list
.Where(x => pi.GetValue(x).ToString().ToLower().Contains(query))
.ToList();
}
You should probably add some error handling though in case someone uses a property that is invalid. For example, you could do (pi?.GetValue(x) ?? string.Empty).ToString().ToLower()… to be on the safe side.
I’ve also moved the query.ToLower() out of the lambda expression to make sure it only runs once. You can also try other case-insensitive ways to check whether query is a substring of the value to avoid having to convert any string. Check out the question “Case insensitive 'Contains(string)'” for more information.
Btw. if you are generally interested in running dynamic queries, you should take a look at dynamic LINQ.
Generics and lambda:
namespace WhereTest
{
class Program
{
static void Main(string[] args)
{
var companies = new[] { new Company { Id = 1, Name = "abc" }, new Company { Id = 2, CompanyAddress1 = "abc" } };
foreach (var company in FilterCompanies(companies, "abc", x => x.Name, x => x.CompanyCity))
{
Console.WriteLine(company.Id);
}
}
static List<Company> FilterCompanies(IEnumerable<Company> unfilteredList, string query, params Func<Company, string>[] properties)
{
return unfilteredList.Where(x => properties.Any(c => c.Invoke(x) == query)).ToList();
}
}
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public string CompanyAddress1 { get; set; }
public string CompanyPostCode { get; set; }
public string CompanyCity { get; set; }
public string CompanyCounty { get; set; }
}
}
Advantages: no reflection, strongly typed code.
You can use GetProperty combined with GetValue
List<Company> FilterCompanies(List<Company> unfilteredList, string fieldToQueryOn, string query)
{
return unfilteredList
.Where(x => x.GetType.GetProperty(fieldToQueryOn).GetValue(x)
.ToString().ToLower().Contains(query.ToLower())).ToList();
}
OR: property accessors using string (same as javascript obj[property])
You can modify your class:
public class Company
{
// just add this code block to all your classes that would need to access
// your function
public object this[string propertyName]
{
get{
Type myType = typeof(Company);
PropertyInfo myPropInfo = myType.GetProperty(propertyName);
return myPropInfo.GetValue(this, null);
}
set{
Type myType = typeof(Company);
PropertyInfo myPropInfo = myType.GetProperty(propertyName);
myPropInfo.SetValue(this, value, null);
}
}
public int Id { get; set; }
public string Name { get; set; }
public string CompanyAddress1 { get; set; }
public string CompanyPostCode { get; set; }
public string CompanyCity { get; set; }
public string CompanyCounty { get; set; }
}
and then you can change your function like this:
List<Company> FilterCompanies(List<Company> unfilteredList, string key, string query)
{
// linq version what ideally would like to archeve
return unfilteredList.Where(x => x[key].ToString().ToLower().Contains(query.ToLower())).ToList();
}
Check this Demo
NOTE:
In order for your function to work, you need to add this code to your classes:
public object this[string propertyName]
{
get{
Type myType = typeof(<YOUR CLASS HERE>);
PropertyInfo myPropInfo = myType.GetProperty(propertyName);
return myPropInfo.GetValue(this, null);
}
set{
Type myType = typeof(<YOUR CLASS HERE>);
PropertyInfo myPropInfo = myType.GetProperty(propertyName);
myPropInfo.SetValue(this, value, null);
}
}
Bonus: you can now retrieve values using myObject["someproperty"] and you can even set their values!

How to map some source properties to a wrapped destination type using AutoMapper?

Suppose you have this source model:
public abstract class SourceModelBase {
}
public class SourceContact : SourceModelBase {
public string FirstName { get; set; }
public string LastName { get; set; }
public KeyValuePair Pair { get; set; }
public SourceAddress Address { get; set; }
}
public class KeyValuePair { // Not derived from SourceModelBase.
public string Key { get; set; }
public string Value { get; set; }
}
public class SourceAddress : SourceModelBase {
public string StreetName { get; set; }
public string StreetNumber { get; set; }
}
Now the destination model should be mapped 1:1 by default (subject to normal AutoMapper configuration), but each thing derived from SourceModelBase should be mapped to a wrapper class class Wrap<T> { T Payload { get; set; } string Meta { get; set; } }.
public abstract class DestinationModelBase {
}
public class DestinationContact : DestinationModelBase {
public string FirstName { get; set; }
public string LastName { get; set; }
public KeyValuePair Pair { get; set; } // Not wrapped, base class not `SourceModelBase`.
public Wrap<DestinationAddress> Address { get; set; }
}
public class DestinationAddress : DestinationModelBase {
public string StreetName { get; set; }
public string StreetNumber { get; set; }
}
Since the contact class itself is derived from SourceModelBase it should be wrapped as well.
The result should have this structure:
Wrap<DestinationContact> Contact
string Meta // Comes from the custom wrapper logic.
DestinationContact Payload
string FirstName
string LastName
KeyValuePair Pair
string Key
string Value
Wrap<DestinationAddress> Address
string Meta // Comes from the custom wrapper logic.
DestinationAddress Payload
string StreetName
string StreetNumber
Obviously this wrapping should nest, illustrated by the fact that the mapped object itself is subject to it and so is its Address property.
For some reason all I keep finding are questions related to mapping from destination to source. I know I have to somehow use ResolveUsing and if the destination type is derived from SourceModelBase, somehow apply custom logic to provide the Wrap<T> value based on the value of the source property.
I don't know where to start at all, though. Especially when the source object itself is specified to be subject of the wrapping logic as well.
What's the best, most AutoMapper-idiomatic way to wrap the nested objects if they meet a condition and at the same time wrap the original object as well if it meets the same condition? I already have the mapper creation abstracted away so I can mold the original object automatically before passing it to the mapper, which may help with subjecting the original object to the resolver as well by doing mapper.Map(new { Root = originalObject }) so the resolver sees the instance of the original object as if it was a value of a property of source object as well, not the source object itself.
According to this issue on AutoMapper GitHub page, there is no direct way to do it.
But there is some workarounds. For example - reflection.
In this case you need to know wrapper type and implement converter for desired types. In this example it's MapAndWrapConverter from TSource to Wrap<TDestination>
CreateWrapMap method creates two bindings:
SourceAddress -> Wrap<DestinationAddress> and SourceContact -> Wrap<DestinationContact> which allow you to map SourceContant to wrapped DestinationContact.
internal class Program
{
public static void Main()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceAddress, DestinationAddress>();
cfg.CreateMap<SourceContact, DestinationContact>();
cfg.CreateWrapMap(
//func selecting types to wrap
type => typeof(DestinationModelBase).IsAssignableFrom(type)
&& !type.IsAbstract,
typeof(Wrap<>),
typeof(MapAndWrapConverter<,>));
});
var mapper = config.CreateMapper();
//Using AutoFixture to create complex object
var fixture = new Fixture();
var srcObj = fixture.Create<SourceContact>();
var dstObj = mapper.Map<Wrap<DestinationContact>>(srcObj);
}
}
public static class AutoMapperEx
{
public static IMapperConfigurationExpression CreateWrapMap(
this IMapperConfigurationExpression cfg,
Func<Type, bool> needWrap, Type wrapperGenericType,
Type converterGenericType)
{
var mapperConfiguration =
new MapperConfiguration((MapperConfigurationExpression)cfg);
var types = Assembly.GetExecutingAssembly().GetTypes();
foreach (var dstType in types.Where(needWrap))
{
var srcType = mapperConfiguration.GetAllTypeMaps()
.Single(map => map.DestinationType == dstType).SourceType;
var wrapperDstType = wrapperGenericType.MakeGenericType(dstType);
var converterType = converterGenericType.MakeGenericType(srcType, dstType);
cfg.CreateMap(srcType, wrapperDstType)
.ConvertUsing(converterType);
}
return cfg;
}
}
public class MapAndWrapConverter<TSource, TDestination>
: ITypeConverter<TSource, Wrap<TDestination>>
{
public Wrap<TDestination> Convert(
TSource source, Wrap<TDestination> destination, ResolutionContext context)
{
return new Wrap<TDestination>
{
Payload = context.Mapper.Map<TDestination>(source)
};
}
}
CreateWrapMap method is a little bit messy, especially the part with finding matching types. But it can be refined according to your needs.

uCommerce set Category Definition Values and Description

Using uCommerce v6.6, Umbraco v7
I'm having trouble setting the category's display name and a custom definition that I have created.
I'm receiving this error:
not-null property references a null or transient value UCommerce.EntitiesV2.Category.ProductCatalog
I think this is b/c there's a property in the CategoryDescription class,
public virtual int CategoryDescriptionId { get; protected set; }
But I don't know how to set this b/c usually when you create objects like this, once you save an ID is created for you (think EF).
Also I'm needing to set the custom definition for the category, "productNumber".
var parentCategory = catalog.Categories.First(x => x.Name.Equals(parentName));
var newCategory = new Category
{
Name = product.Name,
Definition = productDef,
DisplayOnSite = true,
ParentCategory = parentCategory,
ProductCatalog = catalog
};
catalog.Categories.Add(newCategory);
catalog.Save();
var catDescription = new CategoryDescription()
{
DisplayName = product.GetValue<string>("productName"),
Category = newCategory,
};
catDescription.Save(); // ****errors out here*****
var catProperty = new CategoryProperty()
{
Category = newCategory,
DefinitionField = DefinitionField.FirstOrDefault(x => x.Name.Equals("productNumber")),
Value = product.GetValue<string>("productNumber"),
};
catProperty.Save();
All my variables have data, meaning they're not null. It's on the save that's the null. newCategory is successfully created as well each and every time.
Class def for CategoryDescription
public class CategoryDescription : IEntity
{
public CategoryDescription();
public static bool operator !=(CategoryDescription x, CategoryDescription y);
public static bool operator ==(CategoryDescription x, CategoryDescription y);
public virtual Category Category { get; set; }
public virtual int CategoryDescriptionId { get; protected set; }
public virtual int? ContentId { get; set; }
public virtual string CultureCode { get; set; }
public virtual string Description { get; set; }
public virtual string DisplayName { get; set; }
public virtual int Id { get; }
public virtual bool RenderAsContent { get; set; }
public static IQueryable<CategoryDescription> All();
public virtual void Delete();
public static void Delete(Expression<Func<CategoryDescription, bool>> expression);
public override bool Equals(object obj);
public static bool Exists(Expression<Func<CategoryDescription, bool>> expression);
public static IList<CategoryDescription> Find(Expression<Func<CategoryDescription, bool>> expression);
public static CategoryDescription FirstOrDefault(Expression<Func<CategoryDescription, bool>> expression);
public static CategoryDescription Get(object id);
public override int GetHashCode();
public virtual void Save();
public static CategoryDescription SingleOrDefault(Expression<Func<CategoryDescription, bool>> expression);
}
I recommend you use the method AddCategoryDescription on newCategory instance instead of trying to tie up the CategoryDescription with references manually. uCommerce is built on NHibernate and sometimes it can be difficult to find out which property is causing the trouble (As long you're not using stateless sessions; then you have to handle it).
I recall that uCommerce is set up to cascade all saves so if you call Save() at last on your catalog you should be good to go.
EDIT (To answer how to populate a property):
You can populate a (new!) property value by using the following
var definitionField = DefinitionField.FirstOrDefault(x => !x.Deleted && x.Definition.Name == "MyDefinition");
var category = new Category();
category.AddProperty(new CategoryProperty
{
Category = category,
DefinitionField = definitionField,
CultureCode = "en-GB",
Value = "My value"
});
I haven't tested it but I sure it will work. If you want overwrite an existing property value you should find the CategoryProperty in the Category.CategoryProperties collection and then replace the Value property. Be aware if you create the same property twice since it can cause the backend YSOD (Unless they have fixed the unintended feature :) )
Best regards Martin

C# Reflection and Attributes: Bug? I can't get around this

This is in the Immediate console:
prop.GetCustomAttributes(typeof(RequiredParameterAttribute),true)
{BridgeStack.DataContracts.RequiredParameterAttribute[0]}
prop.GetCustomAttributes(typeof(RequiredParameterAttribute),true).Cast<RequiredParameterAttribute>()
{BridgeStack.DataContracts.RequiredParameterAttribute[0]}
[BridgeStack.DataContracts.RequiredParameterAttribute[]]: {BridgeStack.DataContracts.RequiredParameterAttribute[0]}
prop.GetCustomAttributes(typeof(RequiredParameterAttribute),true).Cast<RequiredParameterAttribute>().Any()
false
I get the same results in the application.
prop is Site in:
public class AnswerCollectionQuery : IPagedQuery, ISiteQuery, ISortableQuery, IOrderableQuery, IFilteredQuery
{
public int? Page { get; set; }
public int? PageSize { get; set; }
public string Site { get; set; }
[AllowedSortValues(QuerySortEnum.Activity, QuerySortEnum.Creation, QuerySortEnum.Votes)]
public QuerySortEnum? Sort { get; set; }
public object Min { get; set; }
public object Max { get; set; }
public DateTime? FromDate { get; set; }
public DateTime? ToDate { get; set; }
public QueryOrderEnum? Order { get; set; }
public string Filter { get; set; }
}
Site in turn comes from ISiteQuery
public interface ISiteQuery : IQuery
{
[RequiredParameter]
string Site { get; set; }
}
The awkward part is that the console shows the attribute, allows me to cast it, but I can't retrieve it at all, I get zero as the enumeration's length, which is why .Any() fails, too, .FirstOrDefault() returns null, .First() throws, etc.
Any explanation for this type of behavior?
PD: this works though if I decorate Site with [RequiredAttribute] in the concrete class. But I wanted to make this part of the interface.
Update for clarity:
prop comes exactly from here:
public static IEnumerable<PropertyInfo> GetAllProperiesOfObject(object o)
{
const BindingFlags flags = BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance;
PropertyInfo[] list = o.GetType().GetProperties(flags);
return list;
}
foreach (PropertyInfo prop in Utility.GetAllProperiesOfObject(entity))
This is the case for when prop becomes Site
The zero is because it is returning you a zero-length typed array, meaning: it doesn't have the attribute. You can also see this with Attribute.IsDefined (which will return false).
When using implicit interface implementation, the public property on the class does not automatically gain attributes from the interface that it satisfies. To see the attributes on the interface you would need to use
typeof(ITheInterface).GetProperties()
The Site property on the interface is unrelated to the Site property on the class. If the property on the class must have attributes: add the attributes explicitly.

C# working with decorated members

Take this class for example:
public class Applicant : UniClass<Applicant>
{
[Key]
public int Id { get; set; }
[Field("X.838.APP.SSN")]
public string SSN { get; set; }
[Field("APP.SORT.LAST.NAME")]
public string FirstName { get; set; }
[Field("APP.SORT.FIRST.NAME")]
public string LastName { get; set; }
[Field("X.838.APP.MOST.RECENT.APPL")]
public int MostRecentApplicationId { get; set; }
}
How would I go about getting all of the properties that are decorated with the field attribute, get their types, and then assign a value to them?
This is all done with reflection. Once you have a Type object, you can then get its PropertyInfo with myType.GetProperties(), from there, you can get each property's attributes with GetCustomAttributes(), and from there if you find your attribute, you've got a winner, and then you can proceed to work with it as you please.
You already have the PropertyInfo object, so you can assign to it with PropertyInfo.SetValue(object target, object value, object[] index)
You'll need to use Reflection:
var props =
from prop in typeof(Applicant).GetProperties()
select new {
Property = prop,
Attrs = prop.GetCustomAttributes(typeof(FieldAttribute), false).Cast<FieldAttribute>()
} into propAndAttr
where propAndAttr.Attrs.Any()
select propAndAttr;
You can then iterate through this query to set the values:
foreach (var prop in props) {
var propType = prop.Property.PropertyType;
var valueToSet = GetAValueToSet(); // here's where you do whatever you need to do to determine the value that gets set
prop.Property.SetValue(applicantInstance, valueToSet, null);
}
You would just need to invoke the appropriate reflection methods - try this:
<MyApplicationInstance>.GetType().GetProperties().Where(x => x.GetCustomAttributes().Where(y => (y as FieldAttribute) != null).Count() > 0);

Categories

Resources