Get properties of class by order using reflection - c#

Please refer to this code
public class A : B
{
[Display(Name = "Initial Score Code", Order =3)]
public Code { get; set; }
[Display(Name = "Initial Score Code", Order =2)]
public Name{ get; set; }
............
}
I need to get all properties of class through order by orderAttribute of Display. I have tried with this code to do
var prop = typeof(A)
.GetProperties()
.OrderBy(p => ((DisplayAttribute)p.GetCustomAttributes(typeof(DisplayAttribute), true).FirstOrDefault).Order);
but it causes an error
object reference not to set an instance of object
I assumed this issue because of some property not having "Order" property in "DisplayAttribute" .
How to handle this kind of situation? I need to order all the properties even though some property not having the value of order property.

You are missing brackets () on FirstOrDefault operator. Also you should deal with case when default value is returned. I suggest to select Order value before getting first or default value. That will return 0 for all properties which don't have DisplayAttribute:
var prop = typeof(A)
.GetProperties()
.OrderBy(p => p.GetCustomAttributes(typeof(DisplayAttribute), true)
.Cast<DisplayAttribute>()
.Select(a => a.Order)
.FirstOrDefault());
If you want properties without DisplayAttribute to be last, you can provide Int32.MaxValue as default value to be returned:
.Select(a => a.Order)
.DefaultIfEmpty(Int32.MaxValue)
.First()

Try this:
var props = from p in typeof(A).GetProperties()
let orderAttribute = (DisplayAttribute)(p.GetCustomAttributes(typeof(DisplayAttribute), true))
.FirstOrDefault()
where orderAttribute != null
orderby orderAttribute.Order
select p;
Or:
var props = from p in typeof(A).GetProperties()
let orderAttribute = (DisplayAttribute)(p.GetCustomAttributes(typeof(DisplayAttribute), true))
.FirstOrDefault()
orderby orderAttribute == null ? 0 : orderAttribute.Order
select p;

Here is a much more complete answer that allows you to better control the ordering of PropertyInfo instances without DisplayAttribute attributes:
public class A
{
[Display(Name = "Initial Score Code", Order = 3)]
public int Code
{
get;
set;
}
[Display(Name = "Initial Score Code", Order = 2)]
public string Name
{
get;
set;
}
}
public class PropertyInfoComparer : IComparer<PropertyInfo>
{
public int Compare(PropertyInfo x, PropertyInfo y)
{
var attribute1 = (DisplayAttribute)x.GetCustomAttributes(typeof(DisplayAttribute)).FirstOrDefault();
var attribute2 = (DisplayAttribute)y.GetCustomAttributes(typeof(DisplayAttribute)).FirstOrDefault();
// If the first property has no attribute, sort it first
if (attribute1 == null)
{
return -1;
}
// If the second property has no attribute, sort it first
if (attribute2 == null)
{
return 1;
}
// Compare the Order properties of both attributes
return attribute1.Order.CompareTo(attribute2.Order);
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class DisplayAttribute : Attribute
{
public string Name
{
get;
set;
}
public int Order
{
get;
set;
}
}
Usage:
// Get all the properties of Foo and order them using PropertyInfoComparer
typeof(Foo).GetProperties().OrderBy(arg => arg, new PropertyInfoComparer());

I like the approach with Comparer. However, when I tried it, my iterator went into a dead loop at first. Later, it started throwing exceptions. Also, I optimized it for the case when first property doesn't contain "Order" descriptor to avoid even checking for a second one. I also moved all comments into the class description:
/// <summary>
/// If the first property has no attribute, sort it first
/// If the second property has no attribute, sort it first
/// Compare the Order properties of both attributes
/// </summary>
public class PropertyInfoComparer : IComparer<PropertyInfo>
{
public int Compare(PropertyInfo x, PropertyInfo y)
{
if (x == y) return 0;
var attrX = (DisplayAttribute)x.GetCustomAttributes(typeof(DisplayAttribute)).FirstOrDefault();
int? orderX = attrX?.GetOrder();
if (orderX == null) return -1;
var attrY = (DisplayAttribute)y.GetCustomAttributes(typeof(DisplayAttribute)).FirstOrDefault();
int? orderY = attrY?.GetOrder();
if (orderY == null) return 1;
return ((int)orderX).CompareTo((int)orderY);
}
}
Unfortunately, classes that have properties without the "Order" descriptor lose their "natural" order. Therefore, I ended up checking for any properties that have the "Order" descriptor first. If at least one of them has that descriptor, then do the sorting.

Related

Why does the "contains" method returns an element that is in the list as if it is not?

I have a Product table in my DB. Also, I have Brand and Category tables in my DB which are not related to each other. I want to relate these. In the form UI when I click the one of the Categories, should come the Brands which they have products in the related category.
I tried this way to do this. First, I get my products by categoryID with GetList method then I get these products' brands and I added these brands to pblist list(Brand type). However, some products have the same brands and pblist have repeated brand names. I tried to fix this with contains method but it does not work. Also, I have the same problem in the other part which I try to remove brands not included in pblist from blist(all brands' list). I tried removing item from blist by taking its index with this code: blist.RemoveAt(blist.IndexOf(item)); but this one also not working.It returns -1. But item is in the blist.
public class BrandVM : BaseVM
{
public int ProductCount { get; set; }
}
public class BaseVM
{
public int ID { get; set; }
public string Name { get; set; }
public override string ToString()
{
return this.Name;
}
public class BrandService : ServiceBase, IBrandService
{
public List<BrandVM> GetList(int Count)
{
try
{
var result = GetQuery();
result = Count > 0 ? result.Take(Count) : result;
return result.ToList();
}
catch (Exception ex)
{
return null;
}
}
public List<BrandVM> GetListByCatID(int pCatID)
{
var plist = productService.GetListByCatID(pCatID);
List<BrandVM> pblist = new List<BrandVM>();
foreach (var item in plist)
{
if (!pblist.Contains(item.Brand))
{
pblist.Add(item.Brand);
}
};
var blist = GetList(0);
var blistBackup = GetList(0);
foreach (BrandVM item in blistBackup)
{
if (!pblist.Contains(item))
{
blist.Remove(item);
}
};
return blist;
}
These are my classes related to Brand. In BrandService I shared the filled methods there are more methods to fill.
This is method is in my ProductService:
I use that method to pull product list by CategoryID (plist)
public List<ProductVM> GetListByCatID(int EntityID)
{
try
{
var result = GetQuery().Where(x => x.Category.ID==EntityID);
return result.ToList();
}
catch (Exception ex)
{
return null;
}
}
This GetQuery method for ProductService, in other services there are some differences but there are similar
private IQueryable<ProductVM> GetQuery()
{
return from p in DB.Products
select new ProductVM
{
ID = p.ProductID,
Name = p.ProductName,
UnitPrice = (decimal)p.UnitPrice,
Category =p.CategoryID==null?null:new CategoryVM()
{
ID = (int)p.CategoryID,
Name = p.Category.CategoryName
},
Brand = p.BrandID == null ? null :
new BrandVM
{
ID=(int)p.BrandID,
Name=p.Brand.BrandName,
}
};
}
Entity framework will translate Linq queries into SQL statements, which means that Equals (and GetHashCode) will not be used for comparison of database objects. However, if you're comparing local instances of these objects, then these methods will be used for comparisons.
The default Equals does a reference comparison to determine equality, which literally means that two instances of a type are only considered equal if they both refer to the exact same object in memory.
Instead, we want to use the ID property for equality comparison, which means we need to override the Equals (and GetHashCode) methods for the class.
Here's an example of how you could do this:
public class BaseVM
{
public int ID { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name;
}
public override bool Equals(object obj)
{
return obj is BaseVM &&
((BaseVM) obj).ID == ID;
}
public override int GetHashCode()
{
return ID;
}
}
Alternatively, if you don't want to modify the class (which I would recommend since it solves this problem everywhere), you can modify your code to filter out any brands that have the same id (or name):
foreach (var item in plist)
{
// Note: you could potentially use 'Name' instead of 'Id'
if (!pblist.Any(productBrand => productBrand.Id == item.Brand.Id))
{
pblist.Add(item.Brand);
}
}
Since you don't ensure that two different instances for a same brand are not equal,
in the sense that ´.Equals(object other)´ returns true,
the ´.Contains´ method as no way to identify them.
I think you'ĺl solve you issue by overriding .Equals in you Brand class.

Best way to find values not in two lists c#

I have two lists which I need to compare (carOptions and custOptions).
Both of these lists are in my Customer class like below:
public class CustomerDTO
{
public int CustomerId { get; set; }
//other props removed for brevity
public List<OptionDTO> SelectedCarOptions { get; set; }
public List<OptionDTO> SelectedCustomerOptions { get; set; }
}
var existingData = _myRepository.GetDataByCustomer(customerId, year);
var existingCarOptions = existingData.Select(f => f.SelectedCarOptions);
var existingCustomerOptions = existingData.Select(f => f.SelectedCustomerOptions);
existingData is an IEnumerable of CustomerDTO and then existingCarOptions and existingCustomerOptions is an IEnumerable<List<OptionDTO>>
In the method, I have a list of IEnumerable<OptionDTO> options that gets passed in. I then break this down into car or customer based on the Enum as below:
var newCarOptions = options.Where(o => o.OptionTypeID == OptionType.CarOptions);
var newCustomerOptions = options.Where(o => o.OptionTypeID == OptionType.CustomerOptions).ToList();
What I need to do is find which options are in one collection but no in the other.
I tried as below but getting an Error on the Except (I maybe need to create my own static method in that class) but I am not sure this is the best approach really?
if (existingCarOptions.Count() != newCarOptions.Count())
{
//var test = newCarOptions.Except(existingCarOptions);
}
if (existingCustomerOptions.Count() != newCustomerOptions.Count())
{
//var test2 = newCustomerOptions.Except(existingCustomerOptions);
}
Is it also quite a bit of code in the method - I could split it out into sperate methods if required but perhaps there is a simpler way I could achieve this?
I'm assuming OptionDTO has a property called Id, which uniquely identifies an option (you have to change the code accordingly if this is not the case), you may use HashSets to quickly find unmatched OptionsDTOs, while keeping the overall time cost O(n) (where n is the max number of combined options).
Create the existing options sets:
var existingCarOptions = existingData.SelectMany(d => d.SelectedCarOptions).Select(o => o.Id);
var existingCustomerOptions = existingData.SelectMany(d => d.SelectedCustomerOptions).Select(o => o.Id);
var existingCarOptionsIds = new HashSet<int>(existingCarOptions);
var existingCustomerOptionsIds = new HashSet<int>(existingCustomerOptions );
Then you extract options missing in existing sets with:
var unmatchedCarOptions = newCarOptions.Where(o => !existingCarOptionsIds.Contains(o.Id));
var unmatchedCustomerOptions = newCustomerOptions.Where(o => !existingCustomerOptionsIds.Contains(o.Id));
If you want to compare two classes you can use IEqualityComparer
public class OptionComparer : IEqualityComparer<OptionDTO>
{
public bool Equals(OptionDTO x, OptionDTO y)
{
if (object.ReferenceEquals(x, y))
{
return true;
}
if (object.ReferenceEquals(x, null) ||
object.ReferenceEquals(y, null))
{
return false;
}
return x.OptionTypeID == y.OptionTypeID ;
}
public int GetHashCode(OptionDTO obj)
{
if (obj == null)
{
return 0;
}
return obj.OptionTypeID.GetHashCode();
}
With using this you can ıdentify that What is the concept of equality for these classes.
Now we can find different values..
public List<OptionDTO>CalculateDiffBetweenLists(List<OptionDTO> left, List<OptionDTO> right){
List<OptionDTO> optionDiff;
optionDiff = left.Except(right, new OptionComparer ()).ToList();
return optionDiff ;
}

Check all properties in List

I have List of class as :-
public class Requirement
{
public string Id { get; set; }
public string desc { get; set; }
}
List lstRequirement
I have 3 records in this list for Id and desc.
I wanted to check if any of item is not remaining null.
For that I used below :-
bool IsHavingValidTags = lstRequirement.All(_=> _.Id!=null && _.desc!=null);
This condition is working fine with above Linq.
But I wanted to make it as Generic.
Eg. In future there may get added 5 more properties in Requirement class.
After addition of properties I also have to make changes in Linq.
How can I make this Linq condition generic for all properties?
I want to check any of the property is not remaining null in List.
Please help..!!!
I tried With =>
bool IsHavingValidTags = lstRequirement.All(_ => _ != null);
But not giving desired result.
EDIT 1 :
You can write an extension method that uses reflection like the following:
public static class Extensions
{
public static bool AreAllPropertiesNotNullForAllItems<T>(this IEnumerable<T> items)
{
var properties = typeof(T).GetProperties();
return items.All(x => properties.All(p => p.GetValue(x) != null));
}
}
then use like this:
bool IsHavingValidTags = lstRequirement.AreAllPropertiesNotNullForAllItems();
EDIT:
PropertyInfo.GetValue(object obj) method overload was introduced in .NET Framework 4.5. If you are using .NET Framework 4.0 you need to call p.GetValue(x, null)
Instead of this you should make those field not null. this will never allow those field inserted null. keep validations. like bellow.
[Required(ErrorMessage = "First name is required")]
public string first_name { get; set; }
[Required(ErrorMessage = "Last name is required")]
public string last_name { get; set; }
You can use foreach loop to loop through all the object in the list. Then use reflection to get all the properties in each item in the list, then you can loop through those properties to perform your null check.
Foreach (var x in lstRequirement){
List prop = x.GetType().GetProperties();
Foreach (var y in prop){
If (y == null){
IsHavingValidTag = true;
//Then you can return you method here or throw an Exception
}
}
Hope this helps.
You should add an static method to check the Properties of the Class. I will show you the following example.
Instead of your code :
bool IsHavingValidTags = lstRequirement.All(_ => _ != null);
use the following codes:
bool flg = list.All(m => CheckProperties(m));
public static bool CheckProperties<T>(T source)
{
bool rtnFlg = true;
Type t = typeof(T);
var properties = t.GetProperties();
foreach (var prop in properties)
{
var value = prop.GetValue(source, null);
if (value == null)
{
return false;
}
}
return rtnFlg;
}

How to UnitTest if all Properties are Set. There is no Duplication

Hello I'm trying to test a class that represents layout theme of GUI. It has color and size properties and a method that sets the default values.
public class LayoutTheme : ILayoutTheme
{
public LayoutTheme()
{
SetTheme();
}
public void SetTheme()
{
WorkspaceGap = 4;
SplitBarWidth = 4;
ApplicationBack = ColorTranslator.FromHtml("#EFEFF2");
SplitBarBack = ColorTranslator.FromHtml("#CCCEDB");
PanelBack = ColorTranslator.FromHtml("#FFFFFF ");
PanelFore = ColorTranslator.FromHtml("#1E1E1E ");
// ...
}
public int WorkspaceGap { get; set; }
public int SplitBarWidth{ get; set; }
public Color ApplicationBack { get; set; }
public Color SplitBarBack { get; set; }
public Color PanelBack { get; set; }
public Color PanelFore { get; set; }
// ...
}
I need to test:
1. If all of the properties are set by the SetTheme method.
2. If there is no duplication in setting a property.
For the first test, I first cycle through all properties and set an unusual value. After that I call SetTheme method and cycle again to check if all properties are changed.
[Test]
public void LayoutTheme_IfPropertiesSet()
{
var theme = new LayoutTheme();
Type typeTheme = theme.GetType();
PropertyInfo[] propInfoList = typeTheme.GetProperties();
int intValue = int.MinValue;
Color colorValue = Color.Pink;
// Set unusual value
foreach (PropertyInfo propInfo in propInfoList)
{
if (propInfo.PropertyType == typeof(int))
propInfo.SetValue(theme, intValue, null);
else if (propInfo.PropertyType == typeof(Color))
propInfo.SetValue(theme, colorValue, null);
else
Assert.Fail("Property '{0}' of type '{1}' is not tested!", propInfo.Name, propInfo.PropertyType);
}
theme.SetTheme();
// Check if value changed
foreach (PropertyInfo propInfo in propInfoList)
{
if (propInfo.PropertyType == typeof(int))
Assert.AreNotEqual(propInfo.GetValue(theme, null), intValue, string.Format("Property '{0}' is not set!", propInfo.Name));
else if (propInfo.PropertyType == typeof(Color))
Assert.AreNotEqual(propInfo.GetValue(theme, null), colorValue, string.Format("Property '{0}' is not set!", propInfo.Name));
}
}
Actually the test works well and I even found two missed settings, but I don't think it is written well.
Probably it can be don with Moq of the interface and check if all properties are set.
About the second test, don't have idea how to do it. Probably mocking and checking the number of calls can do it. Any help?
Thank you!
For testing if all properties are set to certain values, I would implement Equals() for this class and create a second object with known values and check for equality. This also comes in handy when testing for state changes etc.
I would certainly not test if a property gets set multiply times if there is no explicit reason to do it.

MSTest: CollectionAssert.AreEquivalent failed. The expected collection contains 1 occurrence(s) of

Question:
Can anyone tell me why my unit test is failing with this error message?
CollectionAssert.AreEquivalent failed. The expected collection contains 1
occurrence(s) of . The actual
collection contains 0 occurrence(s).
Goal:
I'd like to check if two lists are identical. They are identical if both contain the same elements with the same property values. The order is irrelevant.
Code example:
This is the code which produces the error. list1 and list2 are identical, i.e. a copy-paste of each other.
[TestMethod]
public void TestListOfT()
{
var list1 = new List<MyPerson>()
{
new MyPerson()
{
Name = "A",
Age = 20
},
new MyPerson()
{
Name = "B",
Age = 30
}
};
var list2 = new List<MyPerson>()
{
new MyPerson()
{
Name = "A",
Age = 20
},
new MyPerson()
{
Name = "B",
Age = 30
}
};
CollectionAssert.AreEquivalent(list1.ToList(), list2.ToList());
}
public class MyPerson
{
public string Name { get; set; }
public int Age { get; set; }
}
I've also tried this line (source)
CollectionAssert.AreEquivalent(list1.ToList(), list2.ToList());
and this line (source)
CollectionAssert.AreEquivalent(list1.ToArray(), list2.ToArray());
P.S.
Related Stack Overflow questions:
I've seen both these questions, but the answers didn't help.
CollectionAssert use with generics?
Unit-testing IList with CollectionAssert
You are absolutely right. Unless you provide something like an IEqualityComparer<MyPerson> or implement MyPerson.Equals(), the two MyPerson objects will be compared with object.Equals, just like any other object. Since the objects are different, the Assert will fail.
It works if I add an IEqualityComparer<T> as described on MSDN and if I use Enumerable.SequenceEqual. Note however, that now the order of the elements is relevant.
In the unit test
//CollectionAssert.AreEquivalent(list1, list2); // Does not work
Assert.IsTrue(list1.SequenceEqual(list2, new MyPersonEqualityComparer())); // Works
IEqualityComparer
public class MyPersonEqualityComparer : IEqualityComparer<MyPerson>
{
public bool Equals(MyPerson x, MyPerson y)
{
if (object.ReferenceEquals(x, y)) return true;
if (object.ReferenceEquals(x, null) || object.ReferenceEquals(y, null)) return false;
return x.Name == y.Name && x.Age == y.Age;
}
public int GetHashCode(MyPerson obj)
{
if (object.ReferenceEquals(obj, null)) return 0;
int hashCodeName = obj.Name == null ? 0 : obj.Name.GetHashCode();
int hasCodeAge = obj.Age.GetHashCode();
return hashCodeName ^ hasCodeAge;
}
}
I was getting this same error when testing a collection persisted by nHibernate. I was able to get this to work by overriding both the Equals and GetHashCode methods. If I didn't override both I still got the same error you mentioned:
CollectionAssert.AreEquivalent failed. The expected collection contains 1 occurrence(s) of .
The actual collection contains 0 occurrence(s).
I had the following object:
public class EVProjectLedger
{
public virtual long Id { get; protected set; }
public virtual string ProjId { get; set; }
public virtual string Ledger { get; set; }
public virtual AccountRule AccountRule { get; set; }
public virtual int AccountLength { get; set; }
public virtual string AccountSubstrMethod { get; set; }
private Iesi.Collections.Generic.ISet<Contract> myContracts = new HashedSet<Contract>();
public virtual Iesi.Collections.Generic.ISet<Contract> Contracts
{
get { return myContracts; }
set { myContracts = value; }
}
public override bool Equals(object obj)
{
EVProjectLedger evProjectLedger = (EVProjectLedger)obj;
return ProjId == evProjectLedger.ProjId && Ledger == evProjectLedger.Ledger;
}
public override int GetHashCode()
{
return new { ProjId, Ledger }.GetHashCode();
}
}
Which I tested using the following:
using (ITransaction tx = session.BeginTransaction())
{
var evProject = session.Get<EVProject>("C0G");
CollectionAssert.AreEquivalent(TestData._evProjectLedgers.ToList(), evProject.EVProjectLedgers.ToList());
tx.Commit();
}
I'm using nHibernate which encourages overriding these methods anyways. The one drawback I can see is that my Equals method is based on the business key of the object and therefore tests equality using the business key and no other fields. You could override Equals however you want but beware of equality pollution mentioned in this post:
CollectionAssert.AreEquivalent failing... can't figure out why
If you would like to achieve this without having to write an equality comaparer, there is a unit testing library that you can use, called FluentAssertions,
https://fluentassertions.com/documentation/
It has many built in equality extension functions including ones for the Collections. You can install it through Nuget and its really easy to use.
Taking the example in the question above all you have to write in the end is
list1.Should().BeEquivalentTo(list2);
By default, the order matters in the two collections, however it can be changed as well.
I wrote this to test collections where the order is not important:
public static bool AreCollectionsEquivalent<T>(ICollection<T> collectionA, ICollection<T> collectionB, IEqualityComparer<T> comparer)
{
if (collectionA.Count != collectionB.Count)
return false;
foreach (var a in collectionA)
{
if (!collectionB.Any(b => comparer.Equals(a, b)))
return false;
}
return true;
}
Not as elegant as using SequenceEquals, but it works.
Of course to use it you simply do:
Assert.IsTrue(AreCollectionsEquivalent<MyType>(collectionA, collectionB, comparer));

Categories

Resources