Automapper: is it possible to extend same rule to all members? [duplicate] - c#

I want to use automapper to map between my public data contracts and my DB models. I have created a class which automatically goes through all the contracts are creates mappings. The only problem I have is that I only want to map values from the contract to the DB model if the value is not null. I have looked at other question on here but cant see examples that use custom resolvers.
Here is some of my code
var mapToTarget = AutoMapper.Mapper.CreateMap(contract, mappedTo);
foreach (var property in contract.GetProperties().Where(property => property.CustomAttributes.Any(a => a.AttributeType == typeof(MapsToProperty))))
{
var attribute = property.GetCustomAttributes(typeof(MapsToProperty), true).FirstOrDefault() as MapsToProperty;
if (attribute == null) continue;
mapToTarget.ForMember(attribute.MappedToName,
opt =>
opt.ResolveUsing<ContractToSourceResolver>()
.ConstructedBy(() => new ContractToSourceResolver(new MapsToProperty(property.Name, attribute.SourceToContractMethod, attribute.ContractToSourceMethod))));
}
private class ContractToSourceResolver : ValueResolver<IDataContract, object>
{
private MapsToProperty Property { get; set; }
public ContractToSourceResolver(MapsToProperty property)
{
Property = property;
}
protected override object ResolveCore(IDataContract contract)
{
object result = null;
if (Property.ContractToSourceMethod != null)
{
var method = contract.GetType()
.GetMethod(Property.ContractToSourceMethod, BindingFlags.Public | BindingFlags.Static);
result = method != null ? method.Invoke(null, new object[] {contract}) : null;
}
else
{
var property =
contract.GetType().GetProperties().FirstOrDefault(p => p.Name == Property.MappedToName);
if (property != null)
{
result = property.GetValue(contract);
}
}
return result;
}
}
And this is how I want to use it
AutoMapper.Mapper.Map(dataContract, dbModel)
This currently works great but if there is a NULL value in the dataContract then it will replace the existing value in the dbModel, this is not what I want. How do I make AutoMapper ignore null source values?
EDIT
As pointed out in one of the answers there is this
Mapper.CreateMap<SourceType, DestinationType>().ForAllMembers(opt => opt.Condition(srs => !srs.IsSourceValueNull));
This would be ideal except for the fact that .ForAllMembers is not accessible from
Mapper.CreateMap(SourceType, DestinationType)

UPDATE: IsSourceValueNull is not available starting from V5.
If you want all source properties with null values to be ignored you could use:
Mapper.CreateMap<SourceType, DestinationType>()
.ForAllMembers(opt => opt.Condition(srs => !srs.IsSourceValueNull));
Otherwise, you can do something similar for each member.
Read this.

For newer versions that utilize the Instance API, use this instead:
var mappingConfig = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceType, DestinationType>().ForAllMembers(opt => opt.Condition(
(source, destination, sourceMember, destMember) => (sourceMember != null)));
});
Note: This functionality works as of 5.0.2, breaking on later versions as of this writing. Waiting for the next 5.2.x release is recommended if upgrading from 5.0.2.

The solution here works for my project, which is using AutoMapper 6.0.2. In previous projects using AutoMapper 4, I had used IsSourceValueNull to achieve similar behavior. I have nullable types mapped to non-nullable types in my project, this solution is able to handle that scenario.
I made a small change to the original solution. Instead of checking the type of the property to be mapped, I set the filter in ForAllPropertyMaps to check the type of the source object, so that the custom resolver only applies to maps from that source object. But the filter can be set to anything as needed.
var config = new MapperConfiguration(cfg =>
{
cfg.ForAllPropertyMaps(
pm => pm.TypeMap.SourceType == typeof(<class of source object>),
(pm, c) => c.ResolveUsing<object, object, object, object>(new IgnoreNullResolver(), pm.SourceMember.Name));
});
class IgnoreNullResolver : IMemberValueResolver<object, object, object, object>
{
public object Resolve(object source, object destination, object sourceMember, object destinationMember, ResolutionContext context)
{
return sourceMember ?? destinationMember;
}
}

I've got the same exact issue with mapping up the conditionals to the non-generic types. Here's how I solved it.
Wire up:
foreach (PropertyInfo p in type.GetProperties().Where(x => x.GetCustomAttributes<SkipMapIfNullAttribute>().Any()))
map.ForMember(p.Name, x => x.ResolveUsing(typeof(SkipMapIfNullResolver)).FromMember(p.Name));
The second .FromMember is required so the value for the member is passed to the value resolver, rather than the full model.
The resolver looks like this:
public class SkipMapIfNullResolver : IValueResolver
{
public ResolutionResult Resolve(ResolutionResult source)
{
if (source.Value == null)
source.ShouldIgnore = true;
return source;
}
}

Related

Test that property has child validator FluentValidation

The below test fails when I run it. I have an object, instruction that has a number of properties, most of which require their own validators. I want to be able to check that the validators for these child properties are present when set by the parent validator.
[Test]
public void ChildValidatorsSet()
{
_validator.ShouldHaveChildValidator(i => i.Property, typeof(FluentPropertyValidator));
_validator.ShouldHaveChildValidator(i => i.AdditionalInformation, typeof(FluentAdditionalInformationValidator));
}
Within the validator for this class I have the below rules defined that ensure the property in quest has a value set and sets a validator when the property is not null.
public FluentRemortgageInstructionValidator()
{
RuleFor(si => si.Property)
.NotNull().WithMessage("Solicitor Instruction: Instructions must have a linked property.");
RuleFor(si => si.Property)
.SetValidator(new FluentPropertyValidator()).When(si => si.Property != null);
RuleFor(si => si.AdditionalInformation)
.NotNull().WithMessage("Remortgage instructions must have an additional information table.");
RuleFor(si => si.AdditionalInformation)
.SetValidator(new FluentAdditionalInformationValidator()).When(si => si.AdditionalInformation != null);
}
Instruction class:
public class Instruction
{
[Key]
public AdditionalInformation AdditionalInformation { get; set; }
public Property Property { get; set; }
}
}
When an Instruction object with a valid Property property is passed through to the validator the validator should then set validators for Property and AdditionalInformation. Which is what happens when I run my code.
However I am unable to test for this, as there is no way to pass a valid object through to the ShouldHaveChildValidator method, and therefore no child validator is being set.
How do I design a test to check that these child validators are being set properly?
I'll give you an option so you can achieve what you want here, however I have to say that I haven't thought thoroughly about any side effects, so bear that in mind.
Your validators will always be set regardless of the property values, that's why you don't have to pass an instance of any object when calling ShouldHaveChildValidator method. The fact that they get executed or not is another story, and that as you know will depend on your rulesets.
So I cloned the fluent validation git repo and checked out how does the code check for the existence of the child validators.
For this call:
_validator.ShouldHaveChildValidator(i=>i.Property, typeof(FluentPropertyValidator));
This is what is does:
It gets the matching validators for the property expression you pass
in to the method call: i => i.Property
It filters the matching validators to get only those of type IChildValidatorAdaptor.
It throws an error if none of the selected validators are assignable from the type you pass to the method call: FluentPropertyValidator
It seems the code is missing the case where the validator is wrapped by another validator. That's the case of DelegatingValidator class which, by the way, is the type your child validator uses. So one possible solution is to also take those validator types into consideration.
I created a extension method you can use following the same pattern of the original one. Due to my lack of creativity when naming things (naming is tough), I named ShouldHaveChildValidatorCustom. This method is the same method in the code that also calls a couple of another methods that I just copied over from the sources of the FluentValidation so I could add the small modification.
Here is the complete extension class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using FluentValidation.Internal;
using FluentValidation.TestHelper;
using FluentValidation.Validators;
namespace YourTestExtensionsNamespace
{
public static class CustomValidationExtensions
{
public static void ShouldHaveChildValidatorCustom<T, TProperty>(this IValidator<T> validator, Expression<Func<T, TProperty>> expression, Type childValidatorType)
{
var descriptor = validator.CreateDescriptor();
var expressionMemberName = expression.GetMember()?.Name;
if (expressionMemberName == null && !expression.IsParameterExpression())
throw new NotSupportedException("ShouldHaveChildValidator can only be used for simple property expressions. It cannot be used for model-level rules or rules that contain anything other than a property reference.");
var matchingValidators = expression.IsParameterExpression() ? GetModelLevelValidators(descriptor) : descriptor.GetValidatorsForMember(expressionMemberName).ToArray();
matchingValidators = matchingValidators.Concat(GetDependentRules(expressionMemberName, expression, descriptor)).ToArray();
var childValidatorTypes = matchingValidators
.OfType<IChildValidatorAdaptor>()
.Select(x => x.ValidatorType);
//get also the validator types for the child IDelegatingValidators
var delegatingValidatorTypes = matchingValidators
.OfType<IDelegatingValidator>()
.Where(x => x.InnerValidator is IChildValidatorAdaptor)
.Select(x => (IChildValidatorAdaptor)x.InnerValidator)
.Select(x => x.ValidatorType);
childValidatorTypes = childValidatorTypes.Concat(delegatingValidatorTypes);
var validatorTypes = childValidatorTypes as Type[] ?? childValidatorTypes.ToArray();
if (validatorTypes.All(x => !childValidatorType.GetTypeInfo().IsAssignableFrom(x.GetTypeInfo())))
{
var childValidatorNames = validatorTypes.Any() ? string.Join(", ", validatorTypes.Select(x => x.Name)) : "none";
throw new ValidationTestException(string.Format("Expected property '{0}' to have a child validator of type '{1}.'. Instead found '{2}'", expressionMemberName, childValidatorType.Name, childValidatorNames));
}
}
private static IPropertyValidator[] GetModelLevelValidators(IValidatorDescriptor descriptor)
{
var rules = descriptor.GetRulesForMember(null).OfType<PropertyRule>();
return rules.Where(x => x.Expression.IsParameterExpression()).SelectMany(x => x.Validators)
.ToArray();
}
private static IEnumerable<IPropertyValidator> GetDependentRules<T, TProperty>(string expressionMemberName, Expression<Func<T, TProperty>> expression, IValidatorDescriptor descriptor)
{
var member = expression.IsParameterExpression() ? null : expressionMemberName;
var rules = descriptor.GetRulesForMember(member).OfType<PropertyRule>().SelectMany(x => x.DependentRules)
.SelectMany(x => x.Validators);
return rules;
}
}
}
And this is a test that should pass if you set the child validators to your classes and fail otherwise:
[Fact]
public void ChildValidatorsSet()
{
var _validator = new FluentRemortgageInstructionValidator();
_validator.ShouldHaveChildValidatorCustom(i => i.Property, typeof(FluentPropertyValidator));
_validator.ShouldHaveChildValidatorCustom(i => i.AdditionalInformation, typeof(FluentAdditionalInformationValidator));
}
Hope this helps!

Cannot cast back to ICollection<T>

I have an Interface ISomeThing:
public interface ISomeThing {}
and 2 types implementing it:
public class SomeThing1 : ISomeThing {}
public class SomeThing2 : ISomeThing {}
and then I have a type using those types:
public class FooBar
{
public ICollection<SomeThing1> Foo { get; set; }
public ICollection<SomeThing2> Bar { get; set; }
}
I then have to use reflection to get access to the properties of FooBar:
var properties = typeof(FooBar).GetProperties()
.Where(p => typeof(ICollection<>).IsAssignableFrom(p.PropertyType));
Console.WriteLine(properties.Count());
The output will be 0. Even if I change the code to look for ICollection<ISomeThing> it won't work:
var properties = typeof(FooBar).GetProperties()
.Where(p => typeof(ICollection<ISomeThing>).IsAssignableFrom(p.PropertyType));
Console.WriteLine(properties.Count());
I want to get access to the property directly because ICollection brings in Remove etc.. So I need the cast to ICollection<T>.
Edit
I changed my sample to use ISomeThing instead of DateTime. The point is that I don't know the exact generic type at runtime but I need to yield a ICollection<ISomeThing> as the result.
** Edit 2 **
Now I finally come up with my example. This example here shows why I need the cast so badly.
var properties = instance.GetType().GetProperties();
foreach (var property in properties){
var value = property.GetValue(instance);
if (value is ISomeThing someThingValue && someThingValue.IsSomeCondition)
{
// Do a ISomeThing-specific thing here
}
else if (property.PropertyType.GetInterfaces().Concat(new []{property.PropertyType})
.Any(i => i.IsGenericType
&& i.GetGenericTypeDefinition() == typeof(ICollection<>)
&& typeof(ISomeThing).IsAssignableFrom(i.GetGenericArguments().Single())))
{
var someThingValues = value as ICollection<ISomeThing>; // <-- this results in null
foreach (var someThingInstance in someThingValues)
{
if(someThingInstance.IsSomeCondition)
{
// Do the thing again
}
}
}
// Enter recursion for possible nested ISomeThings
}
ICollection<DateTime> collection = new HashSet<ISomeThing>();
That makes no sense. I assume you meant to type
ICollection<ISomeThing> collection = new HashSet<ISomeThing>();
Moving on.
Console.WriteLine(collection.GetType() as ICollection<ISomeThing>);
This is null because collection.GetType() returns a Type, and a Type does not implement ICollection<ISomething>.
Console.WriteLine(typeof(ICollection<>)
.IsAssignableFrom(collection.GetType()));
This is false because IsAssignableFrom means "can a variable of type ICollection<> be assigned a value of type HashSet<ISomething>. There is no such thing as a variable of type ICollection<>. Generic types have to be constructed before they can be the type of a variable.
I feel almost like the output lies to me when I look at line 1 and 3
It does not.
but I want to get access to the property directly because ICollection brings in Remove etc. So I need the cast to ICollection<T>.
I cannot for the life of me figure out what this sentence means. Can you clarify it?
UPDATE:
Based on the update to the question I now suspect that the actual question is either
Given a Type, I wish to know how many properties are of type ICollection<T> where T is any type that implements ISomeThing"
Easy peasy:
Type type = typeof(FooBar);
var properties = type
.GetProperties()
.Where(p => p.PropertyType.IsGenericType)
.Where(p => p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>))
.Where(p => typeof(ISomeThing).IsAssignableFrom(
p.PropertyType.GenericTypeArguments.Single()))
Console.WriteLine(properties.Count()); // 2
or perhaps it is
Given a Type, I wish to know how many properties are of types that implement ICollection<T> where T is any type that implements ISomeThing"
That would be
var properties = type
.GetProperties()
.Where(p => p.PropertyType
.GetInterfaces()
.Concat(new [] {p.PropertyType})
.Where(i => i.IsGenericType)
.Where(i => i.GetGenericTypeDefinition() == typeof(ICollection<>))
.Where(i => typeof(ISomeThing)
.IsAssignableFrom(i.GetGenericArguments().Single()))
.Any());
UPDATE: Based on the latest update to this confusing question, the question is now how to rewrite this loop so that it works:
{
var someThingValues = value as ICollection<ISomeThing>; // <-- this results in null
foreach (var someThingInstance in someThingValues)
{
if(someThingInstance.IsSomeCondition)
{
// Do the thing again
}
}
}
That's easy. You simply don't attempt to cast to ICollection<anything> because you don't need any member of that type in your example. What do we know? That the type is ICollection<T> where T is ISomeThing. It is a requirement that this type implements non-generic IEnumerable and generic IEnumerable<T>.
It is not a requirement that the IEnumerable only yield objects that implement ISomeThing, but the author of the object would be crazy to have IEnumerable yield different objects than could be in the collection, so let's be cautious and stick a type filter on there.
var properties = instance.GetType().GetProperties();
foreach (var property in properties){
// Let's emphasize here that we don't know the type by using
// object instead of var
object value = property.GetValue(instance);
// We need to bail if this is null.
if (value == null)
continue;
if (value is ISomeThing someThingValue && someThingValue.IsSomeCondition)
{
// Do a ISomeThing-specific thing here
}
else if (property.PropertyType.GetInterfaces().Concat(new []{property.PropertyType})
.Any(i => i.IsGenericType
&& i.GetGenericTypeDefinition() == typeof(ICollection<>)
&& typeof(ISomeThing).IsAssignableFrom(i.GetGenericArguments().Single())))
{
var ie = value as IEnumerable;
Debug.Assert(ie != null);
foreach (ISomeThing someThingInstance in ie.OfType<ISomeThing>())
{
if(someThingInstance.IsSomeCondition)
{
// Do the thing again
}
}
}
// Enter recursion for possible nested ISomeThings
}
You could also tighten that up a bit with
var q = ie.OfType<ISomeThing>().
.Where(x => x.IsSomeCondition);
foreach (ISomeThing someThingInstance in q)
{
// Do the thing again
}
BONUS EXERCISE
Now, one might reasonable note that ICollection<T> is also convertible to IEnumerable<T>. We could reason like this:
We know we have ICollection<T> for some T that implements ISomeThing.
We know that we can convert ICollection<T> to IEnumerable<T> for any T.
Therefore we can convert this to IEnumerable<T> for some T that implements ISomeThing.
IEnumerable<T> is covariant in T and therefore we can convert our ICollection<T> directly to IEnumerable<ISomeThing> and enumerate that.
Therefore we should really be writing
var ie = value as IEnumerable<ISomeThing>;
Debug.Assert(ie != null);
foreach (ISomeThing someThingInstance in ie)
The above argument contains a logical flaw. Do you see it?
Give it some thought and once you figure out why this logic is wrong, leave an answer in the comments.
With the updated example, this should get you what you want. It finds all properties that are of ICollection<> and have a generic parameter that implements ISomeThing.
var properties = typeof(FooBar).GetProperties().Where(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>) && p.PropertyType.GenericTypeArguments[0].GetInterfaces().Contains(typeof(ISomeThing)));

How to ignore null values for all source members during mapping in Automapper 6?

I've been looking everywhere: stackoverflow, automapper documentation, internets and just couldn't find any info on this one, even tho this seems to be a very common problem.
My mapping:
CreateMap<StatusLevelDTO, StatusLevel>()
.ForAllMembers(opt => opt.Condition(src => src != null));
This doesn't work because src represents source object (StatusLevelDTO), not a source property (I think).
To be more specific, If I map ObjectA to ObjectB, ObjectA.SomeValue is null and ObjectB.SomeValue is 2, I want the destination object to keep its value (2).
I've seen this question: Automapper skip null values with custom resolver and tried the first two answers but they both seem to be outdated for version 6.
Is there any way to make this happen in Automapper 6? I am using 6.0.2 to be exact.
Method Condition now has five overloads, one of which accepts predicate of type
Func<TSource, TDestination, TMember, bool>
this TMember parameter is the source member. So you can check source member for null:
CreateMap<StatusLevelDTO, StatusLevel>()
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
This might be late, but for those who are still looking, this might solve your problem the same as mine.
I agree with #sergey to use:
CreateMap<StatusLevelDTO, StatusLevel>()
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
But mapping nullable to non nullable will be an issue like int? to int
it will always return 0. to fix it you can convert int? to int in mapping.
CreateMap<int?, int>().ConvertUsing((src, dest) => src ?? dest);
CreateMap<StatusLevelDTO, StatusLevel>()
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
The solution here works for my project, which is using AutoMapper 6.0.2. In previous projects using AutoMapper 4, I had used IsSourceValueNull to achieve the same behavior.
I made a small change to the original solution. Instead of checking the type of the property to be mapped, I set the filter in ForAllPropertyMaps to check the type of the source object, so that the custom resolver only applies to maps from that source object. But the filter can be set to anything as needed.
var config = new MapperConfiguration(cfg =>
{
cfg.ForAllPropertyMaps(
pm => pm.TypeMap.SourceType == typeof(<class of source object>),
(pm, c) => c.ResolveUsing<object, object, object, object>(new IgnoreNullResolver(), pm.SourceMember.Name));
});
class IgnoreNullResolver : IMemberValueResolver<object, object, object, object>
{
public object Resolve(object source, object destination, object sourceMember, object destinationMember, ResolutionContext context)
{
return sourceMember ?? destinationMember;
}
}
I inspired from #Sergey Berezovskiy's answer, and made this configuration for all members of all maps in the main config:
Mapper.Initialize(cfg =>
{
cfg.ForAllMaps((obj, cnfg) => cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)));
}
As I don't have the reputation to comment, I'll add my answer down here for #Sikor #sensei
If you're using a Model that has the nullable data types of your DTO you can use this extension method below to negate the effects of Automapper resorting to the particular data type's default value.
Model examples
public class Foo {
public bool? Example { get; set; }
}
public class FooDto {
public bool Example { get; set; }
}
Extension Method:
public static TTarget MapModelProperties<TTarget, TSource>(this TTarget target, TSource source) where TTarget : class
where TSource : class
{
// Map target into the source, where the source property is null
Mapper.Initialize(cfg =>
{
cfg.CreateMap<TTarget, TSource>()
.ForAllMembers(opt => opt.Condition((src, dest, srcMember, destMember) => destMember == null));
});
Mapper.Map(target, source);
// Map the source into the target to apply the changes
Mapper.Initialize(cfg => cfg.CreateMap<TSource, TTarget>());
Mapper.Map(source, target);
return target;
}
Usage
public class Foo
{
public bool? Example { get; set; }
}
public class FooDto
{
public bool Example { get; set; }
}
public void Example()
{
var foo = new Foo
{
Example = null
};
var fooDto = new FooDto
{
Example = true
};
fooDto.MapModelProperties(foo);
}
This maps the Dto property values into all model's property values that are null. Then maps the model property values back into the Dto, thus only changing the Dto values that are present in the model.

Dynamically Access Properties by Type

I'm trying to access a property that is the same type of which is passed into a generic.
Look at the code:
class CustomClass
{
CustomProperty property {get; set;}
}
class CustomProperty
{
}
Main
{
// Create a new instance of my custom class
CustomClass myClass = new CustomClass();
// Create a new instance of another class that is the same type as myClass.property
CustomProperty myProp = new CustomProperty();
// Call the generic method
DynamicallyAccessPropertyOnObject<CustomProperty>(myProp, myClass);
}
private void DynamicallyAccessPropertyOnObject<T>(this T propertyToAccess, CustomClass class)
{
// I want to access the property (In class) that is the same type of that which is passed in the generic (typeof(propertyToAccess))
// TODO: I need help accessing the correct property based on the type passed in
}
If you can't see from the code. Basically I want to be able to pass in a something into a generic and then access the property on a class that is of the same type as the thing that was passed in.
Is there a good way to do this?
If you need clarification let me know...
You can use Reflection, and LINQ:
private static void DynamicallyAccessPropertyOnObject<T>()
{
var customClass = typeof(CustomClass);
var property = customClass
.GetProperties()
.FirstOrDefault(x => x.PropertyType == typeof(T));
}
If you are doing this for CustomClass only, you can remove both parameters.Then you can call it:
DynamicallyAccessPropertyOnObject<CustomProperty>();
If you want to generalize it, use two generic arguments:
private static void DynamicallyAccessPropertyOnObject<T, K>(K targetObj)
{
var targetType = targetObj.GetType();
var property = targetType
.GetProperties()
.FirstOrDefault(x => x.PropertyType == typeof(T));
if(property != null)
{
var value = (T)property.GetValue(targetObj);
}
}
Then call it:
DynamicallyAccessPropertyOnObject<CustomProperty,CustomClass>(myClass);
If there's only one such property you can do:
var prop = typeof(CustomClass).GetProperties().First(p => p.PropertyType == typeof(T));
object value = prop.GetValue(#class, null);
you can set the value with SetValue:
object valueToSet = ...
prop.SetValue(#class, valueToSet);

Pass object properties to generate an array of properties names

I have a class with quite a lot of properties and I need to post this class property names to a webservice.
Easy solution is just to create that array by hand, as in new[] {"Id", "Name", "Date", "etc"}.
But that's not fun, I would like to have intellisense support. So far I came up with creating an enum with all these properties, then having a helper function which takes array of these enums and calls .ToString() on each and adds to array.
Problem - quite an useless enum and if my class gets updated, I would need to manualy sync that enum with class properties.
Ideal solution in my mind would be to have something like LINQ extension method, where I could pass properties, something like with Select - ToPropertiesArray(x => {x.Id, X.Name, x.Date})
Am I just crazy and this cannot be done and is just plainly stupid? Or a suggestion on how to pass property names with some kind of IntelliSense support?
public class MyClass
{
public int Id{get;set;}
public string S{get;set;}
public double D{get;set;}
}
public static string[] GetPropsNamesArray<T>(Expression<Func<T,Object>> expr)
{
var t = GetObjectType(expr);
var res = t.GetProperties(BindingFlags.Instance|BindingFlags.Public)
.Select(pi => pi.Name)
.ToArray();
return res;
}
public static Type GetObjectType<T>(Expression<Func<T, object>> expr)
{
if ((expr.Body.NodeType == ExpressionType.Convert) ||
(expr.Body.NodeType == ExpressionType.ConvertChecked))
{
var unary = expr.Body as UnaryExpression;
if (unary != null)
return unary.Operand.Type;
}
return expr.Body.Type;
}
and use:
var selectedPropsNames = GetPropsNamesArray<MyClass>(m => new {m.Id,m.S});
var allPropsNames = GetPropsNamesArray<MyClass>(m => m);
As Lars said, you can use reflection. Using reflection in a method also gets you not having to rewrite when the properties collection changes. The beginning of a sample below iterates the public properties of an entity.
System.Reflection.PropertyInfo[] properties = entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
foreach (System.Reflection.PropertyInfo propertyInfo in properties)
{
// ...
}
To have Intellisense support, you can use Expressions:
public static class Helper
{
public static List<string> ToPropertiesArray(params System.Linq.Expressions.Expression<Func<object>>[] exprs)
{
return exprs.Select(expr => ((expr.Body as System.Linq.Expressions.UnaryExpression).Operand
as System.Linq.Expressions.MemberExpression).Member.Name)
.ToList();
}
}
with sample usage:
SomeClass cl = new SomeClass();
var types = Helper.ToPropertiesArray(() => cl.SomeField, () => cl.SomeOtherField);

Categories