Existing code (simplified)
I have this function
public static string[] GetFieldNames<T>(IEnumerable<T> items)
where T : class
{
var properties = typeof(T).GetProperties().Where(p => SystemTypes.Contains(p.PropertyType)); // Only get System types
return properties.Select(p => p.Name).ToArray();
}
So if say I have this class
class MyClass {
public string Name { get; set; }
[Description("The value")]
public int Value { get; set; }
}
I can have code like this
List<MyClass> items = ...; // Populate items somehow
string[] fieldNames = GetFieldNames(items); // This returns ["Name", "Value"]
That works fine.
The problem
I need to get the Description (if it exists), so that GetFieldNames(items) returns ["Name", "The value"]
How do I modify the GetFieldNames() function to read the Description attribute if it exists?
(Please note that this function has been simplified, the real function is much more complex, so please avoid changing the logic)
This should work for you:
return properties.Select(p =>
Attribute.IsDefined(p, typeof(DescriptionAttribute)) ?
(Attribute.GetCustomAttribute(p, typeof(DescriptionAttribute)) as DescriptionAttribute).Description:
p.Name
).ToArray();
NOTE: just add using System.Reflection as GetCustomAttribute is an extension method in .Net 4.5
public static Tuple<string,string>[] GetFieldNames<T>(IEnumerable<T> items) where T : class
{
var result =
typeof (T).GetProperties()
.Where(p => SystemTypes.Contains(p.PropertyType) &&p.GetCustomAttribute<DescriptionAttribute>() != null)
.Select(
p =>
new Tuple<string, string>(p.Name,
p.GetCustomAttribute<DescriptionAttribute>().Description));
return result.ToArray();
}
for earlier version of .Net framework we can use this extension method:
public static class Extension
{
public static T GetCustomAttribute<T>(this System.Reflection.MemberInfo mi) where T : Attribute
{
return mi.GetCustomAttributes(typeof (T),true).FirstOrDefault() as T;
}
}
This is a generic function you can make use of, if the fieldName has description tag attribute it return the value otherwise it return null.
public string GetDescription<T>(string fieldName)
{
string result;
FieldInfo fi = typeof(T).GetField(fieldName.ToString());
if (fi != null)
{
try
{
object[] descriptionAttrs = fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
DescriptionAttribute description = (DescriptionAttribute)descriptionAttrs[0];
result = (description.Description);
}
catch
{
result = null;
}
}
else
{
result = null;
}
return result;
}
Example:
class MyClass {
public string Name { get; set; }
[Description("The age description")]
public int Age { get; set; }
}
string ageDescription = GetDescription<MyClass>(nameof(Age));
console.log(ageDescription) // OUTPUT: The age description
I am using Description attribute a lot so I wrote Nuget for this purpose.
With it you could just call:
typeof(TestClass).GetPropertyDescription("PropertyName");
It also allows to take DescriptionAttribute from class, field, enum and method.
use GetCustomAttributes Method
static void attributecheck()
{
var props = typeof(Product).GetProperties();
foreach (var propertyInfo in props)
{
var att = propertyInfo.GetCustomAttributes(typeof(DescriptionAttribute), true);
if (att.Length >0)
{
}
}
}
Related
I have the following classes,
User:
public class User:Domain
{
[Sortable]
public string FirstName { get; set; }
[Sortable]
public string LastName { get; set; }
[NestedSortable]
public Profile Profile { get; set; }
}
Profile:
public class Profile : Domain
{
[Sortable]
public string BrandName { get; set; }
[NestedSortable]
public Client Client { get; set; }
[NestedSortable]
public ProfileContact ProfileContact { get; set; }
}
Client:
public class Client : Domain
{
[Sortable]
public string Name { get; set; }
}
Profile Contact:
public class ProfileContact : Domain
{
[Sortable]
public string Email { get; set; }
}
I'm writing a recursive function to get all the properties decorated with [Sortable] attribute. This works well when I have a single [NestedSortableProperty] but fails when I have more than one.
Here is my recursive function:
private static IEnumerable<SortTerm> GetTermsFromModel(
Type parentSortClass,
List<SortTerm> sortTerms,
string parentsName = null,
bool hasNavigation = false)
{
if (sortTerms is null)
{
sortTerms = new List<SortTerm>();
}
sortTerms.AddRange(parentSortClass.GetTypeInfo()
.DeclaredProperties
.Where(p => p.GetCustomAttributes<SortableAttribute>().Any())
.Select(p => new SortTerm
{
ParentName = parentSortClass.Name,
Name = hasNavigation ? $"{parentsName}.{p.Name}" : p.Name,
EntityName = p.GetCustomAttribute<SortableAttribute>().EntityProperty,
Default = p.GetCustomAttribute<SortableAttribute>().Default,
HasNavigation = hasNavigation
}));
var complexSortProperties = parentSortClass.GetTypeInfo()
.DeclaredProperties
.Where(p => p.GetCustomAttributes<NestedSortableAttribute>().Any());
if (complexSortProperties.Any())
{
foreach (var parentProperty in complexSortProperties)
{
var parentType = parentProperty.PropertyType;
if (string.IsNullOrWhiteSpace(parentsName))
{
parentsName = parentType.Name;
}
else
{
parentsName += $".{parentType.Name}";
}
return GetTermsFromModel(parentType, sortTerms, parentsName, true);
}
}
return sortTerms;
}
this happens because of the return statement inside the foreach loop. How to rewrite this? with this example I need to get a list of FirstName,LastName,BrandName,Name and Email. But I'm getting only the first four properties except Email.
Now that the above issue is resolved by removing the return statement as posted in my own answer below and also following #Dialecticus comments to use yield return. so I strike and updated the question.
Now I'm running into another issue. The parent class name is wrongly assigned if a class has multiple [NestedSortable] properties.
This method is called first time with User class like var declaredTerms = GetTermsFromModel(typeof(User), null);
Example,
After the first call, the parentsName parameter will be null and [Sortable] properties in User class don't have any effect.
Now for the [NestedSortable] Profile property in User class, the parentsName will be Profile and so the [Sortable] properties in Profile class will have Name as Profile.BrandName and so on.
Name property in final list to be as follows,
Expected Output:
FirstName, LastName, Profile.BrandName, Profile.Client.Name, Profile.ProfileContact.Email
But Actual Output:
FirstName, LastName, Profile.BrandName, Profile.Client.Name, Profile.Client.ProfileContact.Email
Please assist on how to fix this.
Thanks,
Abdul
after some debugging, I removed the return statement inside foreach loop and that fixed the first issue.
changed from,
return GetTermsFromModel(parentType, sortTerms, parentsName, true);
to,
GetTermsFromModel(parentType, sortTerms, parentsName, true);
Then as per #Dialecticus comments removed passing sortTerms as input parameter and removed the parameter inside the code and changed sortTerms.AddRange(...) to yield return.
changed from,
sortTerms.AddRange(parentSortClass.GetTypeInfo()
.DeclaredProperties
.Where(p => p.GetCustomAttributes<SortableAttribute>().Any())
.Select(p => new SortTerm
{
ParentName = parentSortClass.Name,
Name = hasNavigation ? $"{parentsName}.{p.Name}" : p.Name,
EntityName = p.GetCustomAttribute<SortableAttribute>().EntityProperty,
Default = p.GetCustomAttribute<SortableAttribute>().Default,
HasNavigation = hasNavigation
}));
to,
foreach (var p in properties)
{
yield return new SortTerm
{
ParentName = parentSortClass.Name,
Name = hasNavigation ? $"{parentsName}.{p.Name}" : p.Name,
EntityName = p.GetCustomAttribute<SortableAttribute>().EntityProperty,
Default = p.GetCustomAttribute<SortableAttribute>().Default,
HasNavigation = hasNavigation
};
}
also for complex properties, changed from,
GetTermsFromModel(parentType, sortTerms, parentsName, true);
to,
var complexProperties = GetTermsFromModel(parentType, parentsName, true);
foreach (var complexProperty in complexProperties)
{
yield return complexProperty;
}
And for the final issue I'm facing with the name, adding the below code after the inner foreach loop fixed it,
parentsName = parentsName.Replace($".{parentType.Name}", string.Empty);
So here is the complete updated working code:
private static IEnumerable<SortTerm> GetTermsFromModel(
Type parentSortClass,
string parentsName = null,
bool hasNavigation = false)
{
var properties = parentSortClass.GetTypeInfo()
.DeclaredProperties
.Where(p => p.GetCustomAttributes<SortableAttribute>().Any());
foreach (var p in properties)
{
yield return new SortTerm
{
ParentName = parentSortClass.Name,
Name = hasNavigation ? $"{parentsName}.{p.Name}" : p.Name,
EntityName = p.GetCustomAttribute<SortableAttribute>().EntityProperty,
Default = p.GetCustomAttribute<SortableAttribute>().Default,
HasNavigation = hasNavigation
};
}
var complexSortProperties = parentSortClass.GetTypeInfo()
.DeclaredProperties
.Where(p => p.GetCustomAttributes<NestedSortableAttribute>().Any());
if (complexSortProperties.Any())
{
foreach (var parentProperty in complexSortProperties)
{
var parentType = parentProperty.PropertyType;
//if (string.IsNullOrWhiteSpace(parentsName))
//{
// parentsName = parentType.Name;
//}
//else
//{
// parentsName += $".{parentType.Name}";
//}
var complexProperties = GetTermsFromModel(parentType, string.IsNullOrWhiteSpace(parentsName) ? parentType.Name : $"{parentsName}.{parentType.Name}", true);
foreach (var complexProperty in complexProperties)
{
yield return complexProperty;
}
//parentsName = parentsName.Replace($".{parentType.Name}", string.Empty);
}
}
}
I have found some direction for this problem but have not found anything which I can apply to this problem.
I want to filter lists of different types by stated properties they hold. I can use linq to dynamically filter a List by Test.id but I cant manage to filter a List through MyClass.Name
I have these classes.
public class Test
{
public int Id { get; set; }
public MyClass myclass { get; set; }
}
public class MyClass
{
public string Name { get; set; }
}
This is what I'm trying to do.
static void Main(string[] args)
{
var source = new List<Test> {
new Test { Id = 1,myclass = new MyClass() { Name = "bob" } },
new Test { Id = 2,myclass= new MyClass() { Name = "joe" } } };
var x = myFilter(source,"Name", "bob");
Console.WriteLine(x.Count());
}
public static IEnumerable<T> myFilter<T>(List<T> source, string propertyName, string searchString)
{
// get the myclass property then the stated property(Name) value within it
searchString = searchString.ToLower();
return source.Where(s => (s.GetType().GetProperty("myclass")
.GetType().GetProperty(propertyName)
.GetValue(s.GetType().GetProperty("myclass"),null).ToString() ?? " ")
.ToLower().Contains(searchString));
}
The count return 0 when I am expecting 1. for Test.MyClass.Name = "bob"
Is there a solution for this or is there a better way to do it besides reflection?
Thanks
you need to use the PropertyType of the returned myclass property:
public static IEnumerable<T> myFilter<T>(List<T> source, string propertyName, string searchString)
{
// get the myclass property then the stated property(Name) value within it
searchString = searchString.ToLower();
return source.Where(s => (s.GetType().GetProperty("myclass")
.PropertyType.GetProperty(propertyName)
.GetValue(s.GetType().GetProperty("myclass").GetValue(s)).ToString() ?? " ")
.ToLower().Contains(searchString));
}
You should be able to use the following:
var count = source.Count(test =>
string.Compare(test.myClass.Name, "Bob",
StringComparison.CurrentCultureIgnoreCase) == 0);
This will compare the string value of the Name Property and only count where the name is equal to "bob" and it will ignore the case.
If you want to return the Test object instead then you can use the following
var results = source.Where(test =>
string.Compare(test.myClass.Name, "Bob",
StringComparison.CurrentCultureIgnoreCase) == 0);
I want to implement batch delete (for performance reasons) in Entity framework like this:
context.ExecuteStoreCommand("DELETE FROM {0} WHERE {1} = {2}", tableName, columnName, columnValue);
I want to know how to get the column's name from the property.
[Column("ColumnName")]
public int PropertyName { get; set; }
I Also use EF5 with Oracle provider.
You use Reflection.
public class MyClass
{
[Column("ColumnName")]
public int PropertyName { get; set; }
}
Using Reflection:
public static string GetColumnName<T>(string propertyName)
{
string result = null;
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentNullException();
}
var tableType = typeof(T);
var properties = tableType.GetProperties(BindingFlags.GetProperty
| BindingFlags.Instance
| BindingFlags.Public);
var property = properties
.Where(p => p.Name.Equals(propertyName,
StringComparison.CurrentCultureIgnoreCase))
.FirstOrDefault();
if (property == null)
{
throw new Exception("PropertyName not found."); // bad example
}
else
{
result = property.Name; //if no column attribute exists;
var attributes = property.GetCustomAttributes();
var attribute = attributes
.Where(a => a is ColumnAttribute)
.FirstOrDefault() as ColumnAttribute;
if (attribute != null)
{
result = attribute.Name;
}
}
return result;
}
For example:
public class MyClass
{
[Column("ColumnName")]
public int PropertyName { get; set; }
}
var result = GetColumnName<MyClass>("PropertyName");
Console.WriteLine(result);
Result:
ColumnName
I am trying to use the library called FakeO (https://github.com/rally25rs/FakeO)
It works fine except when there is a Guid property. Anyone have an idea what I maybe doing wrong ?
Exceptin I get is : Object of type 'System.Int32' cannot be converted to type 'System.Guid'.
here is the code
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Get a single instance of an object");
var gud = Guid.NewGuid();
var obj1 = FakeO.Create.Fake<SampleClass>(
s => s.UniqueId = FakeO.Data.Random<Guid>(),
s => s.Id = FakeO.Number.Next(),
s => s.PhoneNumber = FakeO.Phone.Number(),
s => s.SomeString = FakeO.String.Random(50));
Console.WriteLine(obj1.ToString() + "\n");
IEnumerable<SampleClass> obj2 = FakeO.Create.Fake<SampleClass>(10, s => s.Id = FakeO.Number.Next(),
s => s.PhoneNumber = FakeO.Phone.Number(),
s => s.SomeString = FakeO.String.Random(50));
foreach (var obj in obj2)
Console.WriteLine(obj.ToString());
Console.ReadKey();
}
}
public class SampleClass
{
public int Id { get; set; }
public string SomeString { get; set; }
public string PhoneNumber { get; set; }
public Guid UniqueId { get; set; }
public override string ToString()
{
var output = "ID={0},SomeString ={1},PhoneNumber = {2}";
return String.Format(output, Id, SomeString, PhoneNumber);
}
}
Guid is value type, and the author did not handle unsupported ValueType properly. He returns 0 for all unsupported value types in the Data.Random method, which is not quite nice for any struct type. According to this StackOverflow question, the last lines of Data.Random should be fixed to
if(t.IsValueType)
{
return Activator.CreateInstance(t);
}
return null;
This will return default value for struct type, which is empty Guid in case of Guid type I believe. If you want to support Guid type, you can add it in Data.Random method just before the final check of ValueType:
if (t == typeof(Guid))
return Guid.NewGuid();
I did not test my solution, but it should do.
It looks like you should be using:
FakeO.Distinct.Guid()
I am trying update a number of properties of one object from another and I wind up repeating this same code over and over again (i am showing an example with Name and LastName but i have 15 other properties with similar code).
But its important to Note that its NOT all properties so i can't blindly just copy everything.
public class Person
{
public bool UpdateFrom(Person otherPerson)
{
if (!String.IsNullOrEmpty(otherPerson.Name))
{
if (Name!= otherPerson.Name)
{
change = true;
Name = otherPerson.Name;
}
}
if (!String.IsNullOrEmpty(otherPerson.LastName))
{
if (LastName!= otherPerson.LastName)
{
change = true;
LastName = otherPerson.LastName;
}
}
return change;
}
}
is there a more elegant way to writing this code?
You could use an Expression to define which field you want to access, the code to handle the updates would look like this:-
Person one = new Person {FirstName = "First", LastName = ""};
Person two = new Person {FirstName = "", LastName = "Last"};
Person three = new Person ();
bool changed = false;
changed = SetIfNotNull(three, one, p => p.FirstName) || changed;
changed = SetIfNotNull(three, one, p => p.LastName) || changed;
changed = SetIfNotNull(three, two, p => p.FirstName) || changed;
changed = SetIfNotNull(three, two, p => p.LastName) || changed;
Note that the order in the || expression matters since .NET will short-circuit the evaluation if it can. Or as Ben suggests in the comments below, use changed |= ... as a simpler alternative.
The SetIfNotNull method relies on this other method that does a bit of Expression magic to convert a getter ino a setter.
/// <summary>
/// Convert a lambda expression for a getter into a setter
/// </summary>
public static Action<T, U> GetSetter<T, U>(Expression<Func<T, U>> expression)
{
var memberExpression = (MemberExpression)expression.Body;
var property = (PropertyInfo)memberExpression.Member;
var setMethod = property.GetSetMethod();
var parameterT = Expression.Parameter(typeof(T), "x");
var parameterU = Expression.Parameter(typeof(U), "y");
var newExpression =
Expression.Lambda<Action<T, U>>(
Expression.Call(parameterT, setMethod, parameterU),
parameterT,
parameterU
);
return newExpression.Compile();
}
public static bool SetIfNotNull<T> (T destination, T source,
Expression<Func<T, string>> getter)
{
string value = getter.Compile()(source);
if (!string.IsNullOrEmpty(value))
{
GetSetter(getter)(destination, value);
return true;
}
else
{
return false;
}
}
Using Func and Action delegates you can do it like this:
public class Person
{
public string Name { get; set; }
public string LastName { get; set; }
public bool UpdateFrom(Person otherPerson)
{
bool change = false;
change = Check(otherPerson.Name, p => p.Name, (p, val) => p.Name = val);
change = change ||
Check(otherPerson.LastName, p => p.LastName, (p, val) => p.LastName = val);
return change;
}
public bool Check(string value, Func<Person, string> getMember, Action<Person, string> action)
{
bool result = false;
if (!string.IsNullOrEmpty(value))
{
if (getMember(this) != value)
{
result = true;
action(this, value);
}
}
return result;
}
}
You can use reflecton to do it.. here's an example implementation (need to add extra code to handle arrays etc.)
public class Person
{
public bool UpdateFromOther(Person otherPerson)
{
var properties =
this.GetType()
.GetProperties(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty
| BindingFlags.GetProperty);
var changed = properties.Any(prop =>
{
var my = prop.GetValue(this);
var theirs = prop.GetValue(otherPerson);
return my != null ? !my.Equals(theirs) : theirs != null;
});
foreach (var propertyInfo in properties)
{
propertyInfo.SetValue(this, propertyInfo.GetValue(otherPerson));
}
return changed;
}
public string Name { get; set; }
}
[Test]
public void Test()
{
var instance1 = new Person() { Name = "Monkey" };
var instance2 = new Person() { Name = "Magic" };
var instance3 = new Person() { Name = null};
Assert.IsFalse(instance1.UpdateFromOther(instance1), "No changes should be detected");
Assert.IsTrue(instance2.UpdateFromOther(instance1), "Change is detected");
Assert.AreEqual("Monkey",instance2.Name, "Property updated");
Assert.IsTrue(instance3.UpdateFromOther(instance1), "Change is detected");
Assert.AreEqual("Monkey", instance3.Name, "Property updated");
}
This is just my comment typed out, you can refer to the comments to your question about further details about this technique.
Define this class:
[AttributeUsage(AttributeTargets.Property)]
public sealed class CloningAttribute : Attribute
{
}
In your Person class:
[Cloning] // <-- applying the attribute only to desired properties
public int Test { get; set; }
public bool Clone(Person other)
{
bool changed = false;
var properties = typeof(Person).GetProperties();
foreach (var prop in properties.Where(x => x.GetCustomAttributes(typeof(CloningAttribute), true).Length != 0))
{
// get current values
var myValue = prop.GetValue(this, null);
var otherValue = prop.GetValue(other, null);
if (prop.PropertyType == typeof(string))
{
// special treatment for string:
// ignore if null !!or empty!!
if (String.IsNullOrEmpty((string)otherValue))
{
continue;
}
}
else
{
// do you want to copy if the other value is null?
if (otherValue == null)
{
continue;
}
}
// compare and only check 'changed' if they are different
if (!myValue.Equals(otherValue))
{
changed = true;
prop.SetValue(this, otherValue, null);
}
}
return changed;
}
You can create generic rewriting tool with will look on properties with particular attribute:
public class Updater
{
public static bool Update(object thisObj, object otherObj)
{
IEnumerable<PropertyInfo> props = thisObj.GetType().GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(UpdateElementAttribute)));
bool change = false;
foreach (var prop in props)
{
object value = prop.GetValue(otherObj);
if (value != null && (value is string || string.IsNullOrWhiteSpace((string)value)))
{
if (!prop.GetValue(thisObj).Equals(value))
{
change = true;
prop.SetValue(thisObj, value);
}
}
}
return change;
}
}
And then just use it:
public class Person
{
public bool UpdateFrom(Person otherPerson)
{
return Updater.Update(this, otherPerson);
}
[UpdateElement]
public string Name { get; set; }
[UpdateElement]
public string LastName { get; set; }
}