Sort according to a custom attribute - c#

Please Consider this code:
public class MyClass
{
[CustomAttributes.GridColumn(1)]
public string Code { get; set; }
[CustomAttributes.GridColumn(3)]
public string Name { get; set; }
[CustomAttributes.GridColumn(2)]
public DateTime? ProductionDate { get; set; }
public DateTime? ProductionExpiredDate { get; set; }
[CustomAttributes.GridColumn(4)]
public int ProductOwner { get; set; }
}
I want to get a dictionary for all properties that have CustomAttributes.GridColumn and sort them by the number in GridColumn attribute and type of them like this:
PropertyName Type
---------------------------------
Code string
ProductionDate DateTime?
Name string
ProductOwner int
How I can do this?
Thanks

Something like this ought to work:
private IDictionary<string, Type> GetProperties<T>()
{
var type = typeof(T);
return type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Select(p => new { Property = p, Attribute = p.GetCustomAttribute<CustomAttributes.GridColumnAttribute>() })
.Where(p => p.Attribute != null)
.OrderBy(p => p.Attribute.Index)
.ToDictionary(p => p.Property.Name, p => p.Property.PropertyType);
}
It first gets all of the public properties, creates an object which contains the property and the attribute, filters the list to only include properties where the attribute exists, sorts by the attribute index, and finally converts it into a dictionary.
I'm assuming the attribute is defined similar to this:
public class GridColumnAttribute : System.Attribute
{
public GridColumnAttribute(int index)
{
this.Index = index;
}
public int Index { get; set; }
}
P.S. GetCustomAttribute<T>() is an extension method that lives in System.Reflection.CustomAttributeExtensions so make sure you include a using System.Reflection;
Try it online

Related

Reflection, get explicit interface property 'backing field' name

Is there a way to get the name of an explicit interface property backing field?
For example, for:
public interface I
{
string PPPP { get; }
}
public class C: I
{
private string _other_field = default!; // random private field, just to fill.
public string S => _s_backing; // random property, just to fill.
private string _s_backing = default!;
string I.PPPP => _s_backing; // <--- looking for this one!
}
For property PPPP I'm looking to figure up the string "_s_backing".
I mean. Is there a way to create this helper:
Helpers.DoSomeReflectionMagic( typeof(C), "PPPP" )
// I expect, it returns: `_s_backing`.
What I tried: I was digging into typeof(C) properties, but I didn't find the backing field anywhere. Maybe there is no way to get it.
The underlying XY problem:
public interface ITree<T>
{
T? Parent { get; }
IEnumerable<T> Children { get; }
}
public class Category: ITree<Category>
{
public string Name { get; set; }
public Category? MyParentCategory { get; set; }
public IEnumerable<Category> MySubCategories { get; set; }
// Implementing interface
Category? ITree<Category>.Parent => MyParentCategory;;
IEnumerable<Category> ITree<Category>.Children => MySubCategories;
}
// dbcontext with fluent api to configure model blah blah
public static class DbContextTreeExtensions
{
public static IEnumerable<T> GetRoots<T>(this MyDbContext ctx)
{
var backingfieldname = // <-- The Y problem
Helpers
.DoSomeReflectionMagic(typeof(T), "Parent");
return
ctx
.Set<T>()
.Where(q => EF.Property<T?>(q, backingfieldname) == null)
.AsEnumerable();
}
}
I would like to do:
var ctx = MyDbContextFactory.NewContext();
var mainCategories = ctx.GetRoots<Category>();

CreateMap<T,U> for Nullable<TMember> to TMember

Lets say I have an internal EF model:
public class EFModel
{
public int key { get; set; }
public string Name { get; set; }
public int? OptionalId { get; set;}
}
And my external facing model with the same data types except that none are Nullable:
public class Extmodel
{
public int key { get; set; }
public string Name { get; set; }
public int OptionalId { get; set; }
}
In general I'd have to go through CreateMap and explicitly cast:
CreateMap<EfModel,ExtModel>
.ForMember(a => a.OptionalId, b => b.MapFrom(c => (int)c.OptionalId))
.ReverseMap();
But imagine having a very large database, there should be some way to speed this up. I tried to a helper function that works on 'System.Reflection' and generics to sort this out for me.
public void MapWrap<T, U>() where T : new()
{
var newT = new T();
var map = CreateMap<T, U>();
foreach (var member in newT.GetType().GetProperties())
{
Type memberType = member.PropertyType;
var nullable = Nullable.GetUnderlyingType(memberType) == null;
var notString = memberType != typeof(string);
if (nullable && notString)
{
map.ForMember(dest => dest.GetType().GetMethod(member.Name),
src => src.MapFrom(
dt => Convert.ChangeType(dt.GetType().GetMethod(member.Name), Nullable.GetUnderlyingType(memberType))));
}
}
map.ReverseMap();
}
Assumptions:
All internal and external names are identical
All castings are strictly Nullable to T
I am getting an error 'Custom configuration for members is only supported for top-level individual members on a type.' Am I thinking about this all wrong?

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!

Sort list of different models by two fields with different type

I have two models:
public class CarRent
{
public string CarName { get; set; }
public string SystemId { get; set; }
public DateTime RentEndDate { get; set; }
}
public class CarPurchase
{
public string CarName { get; set; }
public string SystemId { get; set; }
public decimal Mileage { get; set; }
}
I need to combine them into one list, group by CarName and then inside each group I need to sort models initially by SystemId, but then if models have the same SystemId - I need to sort CarRent models by RentEndDate and CarPurchase by Mileage.
What I have tried:
I defined an interface:
public interface ICarPurchaseOrdered
{
string CarName { get; }
string SystemId { get; }
string Order { get; }
}
and got my models to implement it, the Order property just returns string representation of second order criteria, then I defined a view model:
public class GroupedCardList
{
public string CarName { get; set; }
public IEnumerable<ICarPurchaseOrdered> Cars { get; set; }
}
then I have a grouper that just groups my models:
public class CarGrouper
{
IEnumerable<GroupedCardList> Group(IEnumerable<ICarPurchaseOrdered> cars)
{
return cars.GroupBy(c => c.CarName)
.OrderBy(c => c.Key)
.Select(c => new GroupedCardList()
{
CarName = c.Key,
Cars = c.OrderBy(n => n.SystemId)
.ThenBy(n => n.Order)
});
}
}
But it doesn't work right because it sorts strings and I get the car purchase with Milage=1200 before the car with Milage=90.
I know that example is a little bit contrived but it perfectly represents the issue that I have right now. Please give me some advice.
One way to do it would be to implement a custom IComparer. If you extract a common base class:
public class Car
{
public string CarName { get; set; }
public string SystemId { get; set; }
}
public class CarRent : Car
{
public DateTime RentEndDate { get; set; }
}
public class CarPurchase : Car
{
public decimal Mileage { get; set; }
}
Then a IComparer<Car> implementation might look like this:
public class CarComparer : IComparer<Car>
{
public int Compare(Car x, Car y)
{
// compare by system id first
var order = string.Compare(x.SystemId, y.SystemId);
if (order != 0)
return order;
// try to cast both items as CarRent
var xRent = x as CarRent;
var yRent = y as CarRent;
if (xRent != null && yRent != null)
return DateTime.Compare(xRent.RentEndDate, yRent.RentEndDate);
// try to cast both items as CarPurchase
var xPurc = x as CarPurchase;
var yPurc = y as CarPurchase;
if (xPurc != null && yPurc != null)
return decimal.Compare(xPurc.Mileage, yPurc.Mileage);
// now, this is awkward
return 0;
}
}
You can then pass the comparer instance to List.Sort and Enumerable.OrderBy.
You can use int.Parse and order integers instead of strings
c.OrderBy(n => n.SystemId)
.ThenBy(n => int.Parse(n.Order))
The ICarPurchaseOrdered.Order used to order (then) by is a string type; Hence why the ordering is done alphabetically.
I would suggest to change the type of ICarPurchaseOrdered.Order to object,
so the Orderbycan use the underlying object (eitherDateTimeorDecimal`) to order.
**Update:
Try this
c.OrderBy(n => n.SystemId)
.ThenBy(n => n.GetType())
.ThenBy(n => n.Order);

C# - copying property values from one instance to another, different classes

I have two C# classes that have many of the same properties (by name and type). I want to be able to copy all non-null values from an instance of Defect into an instance of DefectViewModel. I was hoping to do it with reflection, using GetType().GetProperties(). I tried the following:
var defect = new Defect();
var defectViewModel = new DefectViewModel();
PropertyInfo[] defectProperties = defect.GetType().GetProperties();
IEnumerable<string> viewModelPropertyNames =
defectViewModel.GetType().GetProperties().Select(property => property.Name);
IEnumerable<PropertyInfo> propertiesToCopy =
defectProperties.Where(defectProperty =>
viewModelPropertyNames.Contains(defectProperty.Name)
);
foreach (PropertyInfo defectProperty in propertiesToCopy)
{
var defectValue = defectProperty.GetValue(defect, null) as string;
if (null == defectValue)
{
continue;
}
// "System.Reflection.TargetException: Object does not match target type":
defectProperty.SetValue(viewModel, defectValue, null);
}
What would be the best way to do this? Should I maintain separate lists of Defect properties and DefectViewModel properties so that I can do viewModelProperty.SetValue(viewModel, defectValue, null)?
Edit: thanks to both Jordão's and Dave's answers, I chose AutoMapper. DefectViewModel is in a WPF application, so I added the following App constructor:
public App()
{
Mapper.CreateMap<Defect, DefectViewModel>()
.ForMember("PropertyOnlyInViewModel", options => options.Ignore())
.ForMember("AnotherPropertyOnlyInViewModel", options => options.Ignore())
.ForAllMembers(memberConfigExpr =>
memberConfigExpr.Condition(resContext =>
resContext.SourceType.Equals(typeof(string)) &&
!resContext.IsSourceValueNull
)
);
}
Then, instead of all that PropertyInfo business, I just have the following line:
var defect = new Defect();
var defectViewModel = new DefectViewModel();
Mapper.Map<Defect, DefectViewModel>(defect, defectViewModel);
Take a look at AutoMapper.
There are frameworks for this, the one I know of is Automapper:
http://automapper.codeplex.com/
http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/01/22/automapper-the-object-object-mapper.aspx
Replace your erroneous line with this:
PropertyInfo targetProperty = defectViewModel.GetType().GetProperty(defectProperty.Name);
targetProperty.SetValue(viewModel, defectValue, null);
Your posted code is attempting to set a Defect-tied property on a DefectViewModel object.
In terms of organizing the code, if you don't want an external library like AutoMapper, you can use a mixin-like scheme to separate the code out like this:
class Program {
static void Main(string[] args) {
var d = new Defect() { Category = "bug", Status = "open" };
var m = new DefectViewModel();
m.CopyPropertiesFrom(d);
Console.WriteLine("{0}, {1}", m.Category, m.Status);
}
}
// compositions
class Defect : MPropertyGettable {
public string Category { get; set; }
public string Status { get; set; }
// ...
}
class DefectViewModel : MPropertySettable {
public string Category { get; set; }
public string Status { get; set; }
// ...
}
// quasi-mixins
public interface MPropertyEnumerable { }
public static class PropertyEnumerable {
public static IEnumerable<string> GetProperties(this MPropertyEnumerable self) {
return self.GetType().GetProperties().Select(property => property.Name);
}
}
public interface MPropertyGettable : MPropertyEnumerable { }
public static class PropertyGettable {
public static object GetValue(this MPropertyGettable self, string name) {
return self.GetType().GetProperty(name).GetValue(self, null);
}
}
public interface MPropertySettable : MPropertyEnumerable { }
public static class PropertySettable {
public static void SetValue<T>(this MPropertySettable self, string name, T value) {
self.GetType().GetProperty(name).SetValue(self, value, null);
}
public static void CopyPropertiesFrom(this MPropertySettable self, MPropertyGettable other) {
self.GetProperties().Intersect(other.GetProperties()).ToList().ForEach(
property => self.SetValue(property, other.GetValue(property)));
}
}
This way, all the code to achieve the property-copying is separate from the classes that use it. You just need to reference the mixins in their interface list.
Note that this is not as robust or flexible as AutoMapper, because you might want to copy properties with different names or just some sub-set of the properties. Or it might downright fail if the properties don't provide the necessary getters or setters or their types differ. But, it still might be enough for your purposes.
This is cheap and easy. It makes use of System.Web.Script.Serialization and some extention methods for ease of use:
public static class JSONExts
{
public static string ToJSON(this object o)
{
var oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
return oSerializer.Serialize(o);
}
public static List<T> FromJSONToListOf<T>(this string jsonString)
{
var oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
return oSerializer.Deserialize<List<T>>(jsonString);
}
public static T FromJSONTo<T>(this string jsonString)
{
var oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
return oSerializer.Deserialize<T>(jsonString);
}
public static T1 ConvertViaJSON<T1>(this object o)
{
return o.ToJSON().FromJSONTo<T1>();
}
}
Here's some similiar but different classes:
public class Member
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsCitizen { get; set; }
public DateTime? Birthday { get; set; }
public string PetName { get; set; }
public int PetAge { get; set; }
public bool IsUgly { get; set; }
}
public class MemberV2
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsCitizen { get; set; }
public DateTime? Birthday { get; set; }
public string ChildName { get; set; }
public int ChildAge { get; set; }
public bool IsCute { get; set; }
}
And here's the methods in action:
var memberClass1Obj = new Member {
Name = "Steve Smith",
Age = 25,
IsCitizen = true,
Birthday = DateTime.Now.AddYears(-30),
PetName = "Rosco",
PetAge = 4,
IsUgly = true,
};
string br = "<br /><br />";
Response.Write(memberClass1Obj.ToJSON() + br); // just to show the JSON
var memberClass2Obj = memberClass1Obj.ConvertViaJSON<MemberV2>();
Response.Write(memberClass2Obj.ToJSON()); // valid fields are filled
For one thing I would not place that code (somewhere) external but in the constructor of the ViewModel:
class DefectViewModel
{
public DefectViewModel(Defect source) { ... }
}
And if this is the only class (or one of a few) I would not automate it further but write out the property assignments. Automating it looks nice but there may be more exceptions and special cases than you expect.
Any chance you could have both classes implement an interface that defines the shared properties?

Categories

Resources