Linq Complex OrderBy by Props attributes - c#

I have a class with some props tagged with some attributes. I want to display them on a specific order. So far I can put them in a order, but not on the order that I want.
Here is a simple example of the props with the attributes
[IncludeInEditor]
[IsInPk]
ID
[IncludeInEditor(IsReadOnlyOnModify=true)]
Name
[IncludeInEditor]
Address
[IncludeInEditor]
DOB
The order that I want is:
1st - Props with IsInPk attribute
2nd - Props with IncludeInEditor(IsReadOnlyOnModify=true)
3rd - Props with IncludeInEditor
So far I got this with no sucess and not 100% done (still missing the IsReadOnlyOnModify=true part)
var properties =
item.GetType().GetProperties()
.Where(p => p.GetCustomAttributes(true)
.OfType<IncludeInEditorAttribute>()
.Count() > 0)
.Select (x => new
{
Property = x,
Attribute = (IsInPkAttribute)Attribute.GetCustomAttribute(x, typeof(IsInPkAttribute), true)
})
.OrderBy(x => x.Attribute != null ? 1 : -1)
.Select(x => x.Property)
.ToArray();

You can create your own IComparer<T> implementation to compare the attributes on each property:
public class AttributeComparer : IComparer<Attribute>
{
public int Comparer(Attribute x, Attribute y)
{
if(x == null) return y == null ? 0 : -1;
if(y == null) return 1;
if(x is IsInPkAttribute) return (y is IsInPkAttribute) ? 0 : 1;
else if(y is IsInPkAttribute) return -1;
else
{
xa = (IncludeInEditorAttribute)x;
ya = (IncludeInEditorAttribute)y;
if(xa.IsReadOnlyOnModify == ya.IsReadOnlyOnModify) return 0;
else return x.IsReadOnlyOnModify ? 1 : -1;
}
}
}
Then your query becomes:
var properties = item.GetType().GetProperties()
.Where(p => p.GetCustomAttributes(true)
.OfType<IncludeInEditorAttribute>()
.Any())
.Select (x => new
{
Property = x,
Attribute = Attribute.GetCustomAttribute(x, typeof(IsInPkAttribute), true) ?? Attribute.GetCustomAttribute(x, typeof(IncludeInEditorAttribute, true))
})
.OrderBy(x => x.Attribute, new AttributeComparer())
.Select(x => x.Property)
.ToArray();

After the help of Lee, finally it´s working. The correct code is:
var properties =
item.GetType().GetProperties()
.Where(p => p.GetCustomAttributes(true)
.OfType<IncludeInEditorAttribute>()
.Any())
.Select(x => new
{
Property = x,
Attribute = Attribute.GetCustomAttribute(x, typeof(IsInPkAttribute), true)
?? Attribute.GetCustomAttribute(x, typeof(IncludeInEditorAttribute), true)
})
.OrderBy(x => x.Attribute, new IncludeInEditorAttributeComparer())
.Select(x => x.Property)
.ToArray();
This code that Lee sent, I made a little change.
public class IncludeInEditorAttributeComparer : IComparer<Attribute>
{
public int Compare(Attribute x, Attribute y)
{
//In this case we can assume that
//won´t have null values
if (x is IsInPkAttribute && !(y is IsInPkAttribute))
return -1;
else if (y is IsInPkAttribute && !(x is IsInPkAttribute))
return 1;
else
{
bool xa = (x is IncludeInEditorAttribute ? (x as IncludeInEditorAttribute).IsReadOnlyOnModify : false);
bool ya = (y is IncludeInEditorAttribute ? (y as IncludeInEditorAttribute).IsReadOnlyOnModify: false);
if (xa && !ya)
return -1;
else if (ya && !xa)
return 1;
else
return 0;
}
}
}

Related

Update multiple values to object list in c#

How to update multiple property of object in list?
foreach(emp e in emps){
list1.Where(x => x.ID == e.ID && x.Salary < 5000).FirstOrDefault().Level = B;
list1.Where(x => x.ID == e.ID && x.Salary < 5000).FirstOrDefault().Hike = e.Hike;
list1.Where(x => x.ID == e.ID && x.Salary < 5000).FirstOrDefault().Salary = (e.Hike + 100)*e.Salary/100 ;
}
I dont want to use multiple in-line query for each field. Also it should update same single object.
Note : e.ID is not unique key. list1 can contain duplicate Ids
You need to query your list only once
foreach (emp e in emps)
{
var item = list.FirstOrDefault(x => x.ID == e.empID && x.Salary > 5000);
if (item != null)
{
item.Level = B;
item.Hike = e.Hike;
item.Salary = (e.Hike + 100) * e.Salary / 100;
}
}
After giving lots of try. Looks like these will work :
foreach(emp e in emps){
int index = list1.FindIndex(x => x.ID == e.ID && x.Salary < 5000);
if(index != -1)
{
list1[index].Level = 'B';
list1[index].Hike = e.Hike;
list1[index].Salary = (e.Hike + 100)*e.Salary/100;
}
}
What you guys feel ?
Replace Foo with your class
foreach (emp e in emps)
{
if (list.FirstOrDefault(x => x.ID == e.empID && x.Salary > 5000) is Foo f)
{
f.Level = B;
f.Hike = e.Hike;
f.Salary = (e.Hike + 100) * e.Salary / 100;
}
}
I wanted a solution that updates the object in place, which I suspect the OP wants too.
int i = yourList.FindIndex(x => x.ID == "123" && x.Salary < 5000);
if (i > 0) {
yourList[i].Level = "foo";
yourList[i].Hike = "bar";
}
No explicit search, no nested loops: (if ID is your primary Key)
foreach(item in list.Join(emps, x=>x.ID, e=>e.empID, (l,e)=> (l,e)).Where(x => x.l.Salary > 5000))
{
item.l.Level = B;
item.l.Hike = item.e.Hike;
item.l.Salary = (item.e.Hike + 100) * item.e.Salary / 100;
}
Append .OfType() if you need to.

List.OrderBy define order in a variable

I have a list containing objects :
class MyType
{
String Name;
String Address;
DateTime BirthDay;
}
List<MyType> myList;
I want to write a function working like this :
public void SortList(int value)
{
// orderValue => this is the variable I need
if (value == 0)
orderValue = MyType.Name; // Sort list by Name
else if (value == 1)
orderValue = MyType.Address; // Sort list by Address
else if (value == 1)
orderValue = MyType.BirthDay; // Sort list by BirthDay
if (orderValue != null)
{
List<MyType> sortedList = myList.OrderBy(orderValue).ToList();
if (Enumerable.SequenceEqual(myList, sortedList))
sortedList = myList.OrderByDescending(orderValue).ToList();
myList = sortedList;
}
}
How can I write my orderValue to make this code working ?
This is just a synthesis of my code, I can provide more details if needed.
OrderBy can accept Func and you can have variable like this
Func<Person, string> orderFunc = x => x.Name;
if (value == 0)
orderFunc = x => x.Name;
else if (value == 1)
orderFunc = x => x.Address;
myList = myList.OrderBy(orderFunc).ToList();
You have several ways of doing it. For example, you can add OrderBy clause inside your conditional statement, like this:
IEnumerable<MyType> data = myList;
if (value == 0)
data = data.OrderBy(x => x.Name); // Sort list by Name
else if (value == 1)
data = data.OrderBy(x => x.Address); // Sort list by Address
res = data.ToList();
You can also do it in a single statement:
res = myList
.OrderBy(x => value == 0 ? x.Name : "")
.ThenBy(x => value == 1 ? x.Address : "")
.ToList();
This is somewhat harder to read, but it will do the work on RDBMS side in EF or LINQ to SQL.
You can try
myList.OrderBy((x) => (value == 0 ? x.Name : x.Address));

Search all properties that are string

Is there anyway to do change the "Where", in which it will automatic check all properties that contain a string instead of adding each property name manually?
items.Where(m => m.Property1.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) >= 0
|| m.Property2.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) >= 0
|| m.Property3.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) >= 0
|| m.Property4?.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) >= 0
|| m.Property5?.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) >= 0
|| m.Property6.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) >= 0
|| m.Property7?.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) >= 0
));
Thanks.
I would write a code using reflection...
public bool MyContains(object instance, string word)
{
return instance.GetType()
.GetProperties()
.Where(x => x.PropertyType == typeof(string))
.Select(x => (string)x.GetValue(instance, null))
.Where(x => x != null)
.Any(x => x.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) >= 0);
}
Then your code would be
items.Where(m=>MyContains(m,word));
Based on L.B answer: I accepted his answer
I broke it down into two functions because there is no need to get
the string properties for each instances in the where.
public static class ObjectUtils
{
public static IEnumerable<PropertyInfo> GetPropertiesByType<TEntity>(TEntity entity, Type type)
{
return entity.GetType().GetProperties().Where(p => p.PropertyType == type);
}
public static bool CheckAllStringProperties<TEntity>(TEntity instance, IEnumerable<PropertyInfo> stringProperties, string word)
{
return stringProperties.Select(x => (string)x.GetValue(instance, null))
.Where(x => x != null)
.Any(x => x.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) >= 0);
}
}
Then
var stringProperties = ObjectUtils.GetPropertiesByType(new Item(), typeof(string));
items.Where(x => ObjectUtils.CheckAllStringProperties(x, stringProperties, word)));

How to accelerate C#/Linq query? [i don't need to get data, i need to get condition]

public int being = 0;
public void Insert(Currency current, int number)
{
being = db.Currency.Where(x => x.ForDate == current.ForDate)
.Where(x => x.TitleId == current.TitleId)
.Where(x => x.Value == current.Value).Count(x=>x.Id > 0);
if (being == 0)
{
db.Currency.AddOrUpdate(current);
}
}
it's my code works so slowly, because of getting date but it is not necessary, i don't know other way.
maybe something like :
db.Currency.Find().Value.Equals(current.Value).where...where...
I think your main problem is the .Count(x => x.Id > 0), which forces the evaluation of all the conditions before and actually get the total number.
If you can, replace it with Any. In that way, it just has to get one row at most:
bool isBeing = db.Currency
.Where(x => x.ForDate == current.ForDate
&& x.TitleId == current.TitleId
&& x.Value == current.Value
&& x.Id > 0
)
.Any();
You can do all your conditions in just one where, and also you can skip having a bool variable to check your conditions
if(db.Currency.Where(x => x.ForDate == current.ForDate
&& x.TitleId == current.TitleId && x.Value == current.Value && x.Id > 0).Any())
{
db.Currency.AddOrUpdate(current);
}

How to combine the multiple part linq into one query?

Operator should be ‘AND’ and not a ‘OR’.
I am trying to refactor the following code and i understood the following way of writing linq query may not be the correct way. Can somone advice me how to combine the following into one query.
AllCompany.Where(itm => itm != null).Distinct().ToList();
if (AllCompany.Count > 0)
{
//COMPANY NAME
if (isfldCompanyName)
{
AllCompany = AllCompany.Where(company => company["Company Name"].StartsWith(fldCompanyName)).ToList();
}
//SECTOR
if (isfldSector)
{
AllCompany = AllCompany.Where(company => fldSector.Intersect(company["Sectors"].Split('|')).Any()).ToList();
}
//LOCATION
if (isfldLocation)
{
AllCompany = AllCompany.Where(company => fldLocation.Intersect(company["Location"].Split('|')).Any()).ToList();
}
//CREATED DATE
if (isfldcreatedDate)
{
AllCompany = AllCompany.Where(company => company.Statistics.Created >= createdDate).ToList();
}
//LAST UPDATED DATE
if (isfldUpdatedDate)
{
AllCompany = AllCompany.Where(company => company.Statistics.Updated >= updatedDate).ToList();
}
//Allow Placements
if (isfldEmployerLevel)
{
fldEmployerLevel = (fldEmployerLevel == "Yes") ? "1" : "";
AllCompany = AllCompany.Where(company => company["Allow Placements"].ToString() == fldEmployerLevel).ToList();
}
Firstly, unless AllCompany is of some magic custom type, the first line gives you nothing.
Also I have a doubt that Distinctworks the way You want it to. I don't know the type of AllCompany but I would guess it gives you only reference distinction.
Either way here'w what I think You want:
fldEmployerLevel = (fldEmployerLevel == "Yes") ? "1" : "";
var result = AllCompany.Where(itm => itm != null)
.Where(company => !isfldCompanyName || company["Company Name"].StartsWith(fldCompanyName))
.Where(company => !isfldSector|| fldSector.Intersect(company["Sectors"].Split('|')).Any())
.Where(company => !isfldLocation|| fldLocation.Intersect(company["Location"].Split('|')).Any())
.Where(company => !isfldcreatedDate|| company.Statistics.Created >= createdDate)
.Where(company => !isfldUpdatedDate|| company.Statistics.Updated >= updatedDate)
.Where(company => !isfldEmployerLevel|| company["Allow Placements"].ToString() == fldEmployerLevel)
.Distinct()
.ToList();
Edit:
I moved Distinct to the end of the query to optimize the processing.
How about trying like this;
AllCompany = AllCompany .Where(company => (company => company.Statistics.Created >= createdDate)) && (company.Statistics.Updated >= updatedDate));
If every part of query is optional (like created date, last update date..) then you can build linq query string.
Here's a sneaky trick. If you define the following extension method in its own static class:
public virtual IEnumerable<T> WhereAll(params Expression<Predicate<T> filters)
{
return filters.Aggregate(dbSet, (acc, element) => acc.Where(element));
}
then you can write
var result = AllCompany.WhereAll(itm => itm != null,
company => !isfldCompanyName || company["Company Name"].StartsWith(fldCompanyName),
company => !isfldSectorn || fldSector.Intersect(company["Sectors"].Split('|')).Any(),
company => !isfldLocation || fldLocation.Intersect(company["Location"].Split('|')).Any(),
company => !isfldcreatedDate || company.Statistics.Created >= createdDate,
company => !isfldUpdatedDate || company.Statistics.Updated >= updatedDate,
company => !isfldEmployerLevel || company["Allow Placements"].ToString() == fldEmployerLevel)
.Distinct()
.ToList();

Categories