List.OrderBy define order in a variable - c#

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));

Related

In C#, how can I sort a collection of objects by multiple fields of that object?

I have a list of project objects and I want to sort the collection by using a few different fields in the project object.
IEnumerable<Project> list = GetProjectList();
public class Project
{
public DateTime? Date;
public string ImportanceLevel;
}
I first want to
Sort by the date field (sooner dates should be at the top of the list versus dates farther out in the future) and those should show up before any item without a date.
For the items that don't have Dates set them i want to sort by ImportanceLevel (which can be one of 3 strings (Low, Medium or High) where High would go first and then Medium and then Low
I appreciate this would be better if these were integer values but unfortunately they are stored as strings right now).
One option is
var sorted = list.OrderBy(l => l.Date.HasValue ? l.Date : DateTime.MaxValue)
.ThenBy(l => l.ImportanceLevel == "High" ? 1 :
(l.ImportanceLevel == "Medium" ? 2 : 3));
Here, it will do what you want, also it'll sort the projects with same date, by importance.
Or,
var sorted2 = list.OrderBy(l => l.Date.HasValue ?
int.Parse(l.Date.Value.ToString("yyyyMMdd")) :
(l.ImportanceLevel == "High" ?
100000001 :
(l.ImportanceLevel == "Medium" ? 100000002 : 100000003)));
Where, it'll not sort the projects which have date, by importance.
//Order by the projects with Date
var projectsWithDate = list.Where(d => d.Date != null)
.OrderBy(s => s.Date).ToList();
// Projects without Dates
var withoutdate = list.Where(d => d.Date == null)
.OrderBy(g =>
{
if (g.ImportanceLevel == "High")
return 1;
if (g.ImportanceLevel == "Medium")
return 2;
if (g.ImportanceLevel == "Low")
return 3;
return 0;
})
.ToList();
//Add the second list to first.
projectsWithDate.AddRange(withoutdate);
// Now you can use projectsWithDate collection.
This will work
var orderedList = list
.OrderBy(p => p.Date)
.ThenByDescending(p =>
{
if (p.ImportanceLevel == "Low")
return 1;
if (p.ImportanceLevel == "Medium")
return 2;
if (p.ImportanceLevel == "Hight")
return 3;
return 0;
});
public class Project
{
public DateTime? Date;
public string ImportanceLevel;
}
var sortedProejcts =
projects.OrderByDescending(p => p.Date.HasValue)
.ThenBy(p => Math.Abs(DateTime.Now.CompareTo(p.Date ?? DateTime.MaxValue)))
.ThenByDescending(
p =>
{
switch (p.ImportanceLevel)
{
case "Low":
return 1;
case "Medium":
return 2;
case "High":
return 3;
default:
return 0;
}
});

Linq Converting String to List

Hi I am getting the following error with my Linq query.
Cannot implicitly convert type 'System.Collections.Generic.List<string>'
to 'System.Collections.Generic.List<CTS.Domain.OCASPhoneCalls>'
I know what it means, but I'm unsure how to fix it. Can someone help me with my query? I'm really new to linq.
public List<OCASPhoneCalls> getPhoneLogs2()
{
using (var repo = new OCASPhoneCallsRepository(new UnitOfWorkCTS()))
{
List<OCASPhoneCalls> phone = repo.AllIncluding(p => p.OCASStaff)
.Where(y => y.intNIOSHClaimID == null)
.Select(w => w.vcharDiscussion.Substring(0, 100) + "...")
.ToList();
return phone;
}
}
You are selecting a single property with
.Select(w => w.vcharDiscussion.Substring(0, 100) + "...")
This would return you IEnumerable<string> and calling ToList would return you List<string> NOT List<OCASPhoneCalls>.
If you are returning formatted strings then your method return type should be List<string> like:
public List<string> getPhoneLogs2()
{
using (var repo = new OCASPhoneCallsRepository(new UnitOfWorkCTS()))
{
List<string> phone = repo.AllIncluding(p => p.OCASStaff)
.Where(y => y.intNIOSHClaimID == null)
.Select(w => w.vcharDiscussion.Substring(0, 100) + "...")
.ToList();
return phone;
}
}
You are selecting a List<string> but you are declaring a List<OCASPhoneCalls>, i assume you want to shorten the vcharDiscussion:
List<OCASPhoneCalls> phones = = repo.AllIncluding(p => p.OCASStaff)
.Where(p => p.intNIOSHClaimID == null)
.ToList();
phones.ForEach(p => p.vcharDiscussion = p.vcharDiscussion.Length > 100 ?
p.vcharDiscussion.Substring(0, 100) + "..." :
p.vcharDiscussion);
return phones;
Edit: "I'm getting a null error. vcharDiscussion is coming up null"
Then you need to check that:
phones.ForEach(p => p.vcharDiscussion =
p.vcharDiscussion != null && p.vcharDiscussion.Length > 100 ?
p.vcharDiscussion.Substring(0, 100) + "..." :
p.vcharDiscussion ?? "");
`.Select(w => w.vcharDiscussion.Substring(0, 100) + "...")`
because select it's projection and it will return a list of string and your method expect to return
List<OCASPhoneCalls>

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();

Linq Complex OrderBy by Props attributes

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;
}
}
}

Select using linq based on 2 conditions

I have a collection. I want to select values that matches 2 conditions.
var a;
var b;
foreach(SomeObject so in collection)
{
if(so.Value == something)
{
a = so;
// break if b is not null.
}
else if(so.Value == somethingElse)
{
b = so;
// break if a is not null
}
}
Is it possible to do the above using linq iterating over the collection only once? There will be only one value of something and one value of somethingElse, if we can use that fact.
This solution narrows the list down to two elements and then goes from there. Take(2) is used so the collection only gets searched until the two values are found.
var matches = collection.Where(x => x.Value == something || x.Value == somethingElse)
.Take(2)
.ToList();
var a = matches.Single(x => x.Value == something);
var b = matches.Single(x => x.Value == somethingElse);
var relevant =
from so in l where so.Value == something || so.Value == somethingElse
group so by so.Value == something into grp
select new {ForA = grp.Key, SO = grp.First()};
foreach(var bit in relevant)
if(bit.ForA)
a = bit.SO;
else
b = bit.SO;
It could gain you something against some sources, in that only two items are retrieved, though if against a collection in memory I'd stay with what you had, but adding a catch so that once you'd set both a and b, you stopped looping rather than kept needlessly examining the collection.
You need something like this:
var a = collection.OfType<YourCollectionElementType>().FirstOrDefault(i=>i.Equals(something));
var b = collection.OfType<YourCollectionElementType>().FirstOrDefault(i=>i.Equals(somethingelse));
Your collection should implement IEnumerable at least to be able to use this code.
It depends on what the type of your collection is. If it implements generic IEnumerable<T>, say it's List<YourCollectionElementType> or an array YourCollectionElementType[] then you don't need to use OfType<T>, i.e.
var a = collection.FirstOrDefault(i=>i.Equals(something));
var b = collection.FirstOrDefault(i=>i.Equals(somethingelse));
If your collection doesn't contain that value, a and/or b would get null values.
Actually you can read all these things in MSDN. LINQ is not that hard to learn, if you try
For example:
Enumerable.FirstOrDefault Method (IEnumerable)
Enumerable.OfType Method
EDIT
In your comment you're saying that It is assured that only one value will be present. Is it of great importance that you need two separate variables? You could get the present value just like this:
object thevalue = collection.FirstOrDefault(i => i == something || i == somethingelse);
EDIT
Actually, I'd leave your loop as it is, only having added a line like this:
SomeObject a;
SomeObject b;
foreach(SomeObject so in collection)
{
if(so.Value == something)
a = so;
else if(so.Value == somethingElse)
b = so;
if(a!=null && b!=null)
break;
}
And if only one of the values is expected, then
SomeObject a;
SomeObject b;
foreach(SomeObject so in collection)
{
if(so.Value == something)
{
a = so;
break;
}
else if(so.Value == somethingElse)
{
b = so;
break;
}
}
You could do this:
collection
.Select(x => x.Value == something
? () => a = x
: (x.Value == somethingElse
? () => b = x
: (Action)null))
.Where(x => x != null)
.ToArray()
.ForEach(x => x());
I tested this code and it worked a treat.
If the collection is out on a database somewhere then I suggest this:
collection
.Where(x => x.Value == something || x.Value == somethingElse)
.ToArray()
.Select(x => x.Value == something
? () => a = x
: (x.Value == somethingElse
? () => b = x
: (Action)null))
.Where(x => x != null)
.ToArray()
.ForEach(x => x());
Sure you can, but I wouldn't recommend it. It would be cleaner to have your loop as you already have it now.
If you really wanted to know how, you could do this:
class SomeObject
{
public string Value { get; set; }
}
var items = new[]
{
new SomeObject { Value = "foo" },
new SomeObject { Value = "bar" },
new SomeObject { Value = "baz" },
};
var something = "foo";
var somethingElse = "baz";
var result = items.Aggregate(
Tuple.Create(default(SomeObject), default(SomeObject)),
(acc, item) => Tuple.Create(
item.Value == something ? item : acc.Item1,
item.Value == somethingElse ? item : acc.Item2
)
);
// result.Item1 <- a == new SomeObject { Value = "foo" }
// result.Item2 <- b == new SomeObject { Value = "baz" }
Not sure If I understand your question but assume Something and SomethingElse are value type and known before the run-time, you can do like below in one statement.
var FindItems = new[]
{
new SomeObject { Value = "SomethingElse" }
};
var CollectionItems = new[]
{
new SomeObject { Value = "Something" },
new SomeObject { Value = "SomethingElse" }
};
var qSomeObject = CollectionItems
.Where(c => FindItems.Any(x => c.Value == x.Value));

Categories