lets say I have a list of CustomObjects such that
List<CustomObject> list = new List<CustomObject>();
public class CustomObject
{
public int page {get;set;}
public bool configured {get;set;}
public bool searchable {get;set;}
<more bool properties>
}
and I want to be able to sort or filter by these boolean values. But not all of the properties are required. so, some are nullable. I would like to build a dynamic query that simply chains together OrderBy() and ThenBy()
I thought maybe I should create a tuple to help.
List<Tuple<string, bool>> expressionTuple = new List<Tuple<string, bool>>();
// so now I have a list of tuples.
// so based on whether or not my method parameters are null or not I populate the list
if(ShouldFilter(methodParameter))
{
expressionTuple.Add(new Tuple<string, bool>("ColumnName", methodParameter)));
}
So I was thinking about some kind of chanined custom sort function like so:
private IEnumerable<T> CustomSort<T>(IEnumerable<T> data, List<Tuple<string, bool>> sortExpression)
{
for(int i = 0; i < sortExpression.Count; i++)
{
int index = i; // see if this is the first iteration
// build an expression that would be similar to:
if(index==0)
{
data = list.OrderBy(x=>x.ColumnName == booleanValue);
}else
{
data = list.ThenBy(x=>x.ColumnNam == booleanValue);
}
}
}
I'm not sure how to accomplish this. maybe there is an easier way??
Why? If you're using different orderings on different places, just put the LINQ-expressions at these various places?
Seems to me like you're trying to complicate something that's not really that complicated.
data.OrderBy(p => p.Page)
.ThenBy(p => p.Configured)
.ThenBy(...);
Related
Right so i have a class I'm using to store a set of values
public class dataSet
{
public int Number;
public double Decimal;
public string Text;
//etc...
}
Then I've made an array of type dataSet
public static dataSet[] dataOne = new dataSet[100];
And i'm trying to sort the array of dataOne relevant to the values stored in the int Number stored within dataSet.
I have a sort algorithm ready but i'm struggling to pass in the values stored solely in dataOne.Number so it just ends up being an integer array that i'm passing to the sort.
I'm a total noob at programming so any help would be greatly appreciated.
Edit:
I need to call my sort function by passing it in the array of dataOne.Number if this is possible? So it's basically just passing the sort function an int[]
Give you already have data into your array named dataOne, you could try:
Linq Solution
Use linq to sort it, try this:
dataOne = dataOne.OrderBy(x => x.Number).ToArray();
Remember to add the namespace System.Linq to have access into these methods.
OrderBy allows you to pass an expression to sort data and it will return an IOrderedEnumerable. The ToArray will convert it to an array.
Not Linq Solution
If you are not allowed to use Linq. You could implement an class that implements IComparer<T> and implement the method Compare which takes two generics arguments. Use an instance of this comparer type to sort your data.
For sample, since you have your dataSet type defined, you could implement the comparer:
public class DataSetComparer : IComparer<dataSet>
{
public int Compare(dataSet x, dataSet y)
{
// define the logic to sort here...
return x.Number.CompareTo(y.Number);
}
}
And then, use the comparer on the Array.Sort method:
Array.Sort(dataSet, new NumberComparer());
It will order your dataSets.
I'm not sure I follow why you can't use Linq. But that forces you do to something like this:
var numberValues = new List<int>();
foreach(var dataItem in dataOne)
{
numberValues.Add(dataItem.Number);
}
Then you could pass numberValues.ToArray() to your sort method.
With Linq it would just be
dataOne.Select(d => d.Number).ToArray()
You should have dataset implement IComparable that way you can easily just do...
dataOne = dataOne.OrderBy(x => x).ToArray();
OR...
Array.Sort(dataOne);
Here is how to implement IComparable...
public class dataSet : IComparable
{
public int Number;
public double Decimal;
public string Text;
public int CompareTo(object obj)
{
if (obj == null)
return 1;
dataSet other = obj as dataSet;
if (other != null)
return this.Number.CompareTo(other.Number);
else
throw new ArgumentException("Object is not a dataSet");
}
}
I apologize upfront, because I now realize that I have completely worded my example wrong. For those who have given responses, I truly appreciate it. Please let me re-attempt to explain with a more accurate details. Please edit your responses, and once again, I apologize for not being more exact in my previous posting.
Using an entity framework model class called Staging (which is a representation of my Staging table), I have the following List<Staging>.
List<Staging> data = (from t in database.Stagings select t).ToList();
//check for an empty List...react accordingly...
Here is a quick look at what Staging looks like:
public partial class Staging
{
public int ID { get; set; } //PK
public int RequestID { get; set; } //FK
...
public string Project { get; set; }
...
}
Let us suppose that the query returns 10 records into my data list. Let us also suppose that data[3], data[6], and data[7] each have the same value in data.Project, let's say "Foo". The data.Project value is not known until runtime.
Given this, how would I keep the first occurrence, data[3], and remove data[6] and data[7] from my List<Staging>?
Edit:
I have the following code that works, but is there another way?
HashSet<string> projectValuesFound = new HashSet<string>();
List<Staging> newData = new List<Staging>();
foreach (Staging entry in data)
{
if (!projectValuesFound.Contains(entry.Project))
{
projectValuesFound.Add(entry.Project);
newData.Add(entry);
}
}
You can do this via LINQ and a HashSet<T>:
var found = new HashSet<string>();
var distinctValues = theList.Where(mc => found.Add(mc.Var3));
// If you want to assign back into the List<T> again:
// theList = distinctValues.ToList();
This works because HashSet<T>.Add returns true if the value was not already in the set, and false if it already existed. As such, you'll only get the first "matching" value for Var3.
var uniques = (from theList select theList.Var3).Distinct();
That will give you distinct values for all entries.
You could use Linq:
var result = (from my in theList where my.Var3 == "Foo" select my).First();
If you also want to keep the other items, you can use Distinct() instead of First(). To use Dictinct(), either MyClass must implement IEquatable<T>, or you must provide an IEqualityComparer<T> as shown in the MSDN link.
The "canonical" way to do it would be to pass appropriately implemented comparer to Distinct:
class Var3Comparer : IEqualityComparer<MyClass> {
public int GetHashCode(MyClass obj) {
return (obj.Var3 ?? string.Empty).GetHashCode();
}
public bool Equals(MyClass x, MyClass y) {
return x.Var3 == y.Var3;
}
}
// ...
var distinct = list.Distinct(new Var3Comparer());
Just beware that while current implementation seems to keep the ordering of the "surviving" elements, the documentation says it "returns an unordered sequence" and is best treated that way.
There is also a Distinct overload that doesn't require a comparer - it just assumes the Default comparer, which in turn, will utilize the IEquatable<T> if implemented by MyClass.
I want to take some elements by checking them with my custom function.
I have Person table:
public class Person
{
public int Id { get; set; }
public DateTime BirthDay { get; set; }
public string Name { get; set; }
...
}
I should to use my GetAge() and other functions to filter Persons list.
My following code doesnt work:
public List<Person> FilterPersons(int ageFrom, int ageTo...etc..)
{
var all = Database.Persons.AsQueryable();
all = from item in all
where GetAge(item.BirthDay) > ageFrom
select item;
all = from item in all
where GetAge(item.BirthDay) < ageTo
select item;
// other operations
...
}
I think I can write so. In every step to do this:
List<Person> newList = new List<Person>();
foreach (var item in all)
{
var itemAge = Common.GetAge(item.BirthDay);
if (itemAge > AgeFrom)
{
newList.Add(item);
}
}
all = newList.List();
But this is not best way I think, because I should do filter by many criteries. It will work with low speed.
How can I use my functions in Linq query?
Edit:
I showed GetAge() function for example. I have many functions like that. I wanted to know how to use my function.
Well, you can't.
If you want to have criteria used in Where clause of your SQL query, you need to write them directly as a linq.Expression so that entity may parse it and transform it into SQL, not an external function.
Somthing like this works :
DateTime date = DateTime.Now.AddDays(ageFrom);
all = from item in all
where item.BirthDay > date
select item;
Query Expressions are built in to the C# compiler and as such, it only understands the expression that are built in to the compiler.
For example, when you use the where keyword, it converts that to a call to the Where<TSource>(this IQueryable<TSource> source, Func<TSource, bool> predicate) method.
This is true of Linq To Objects and Linq To SQL. What's more, with Linq To SQL, the compiler then has to convert the Query Expression to SQL, which has no way of knowing the definition of your GetAge method.
Or you can use this syntax:
DateTime date = DateTime.Now.AddDays(ageFrom);
all = item.Where(x => x.BirthDay > date).ToList();
Why not use a List<Person>.FindAll() method and pass in a method filter as the predicate?
You would use the method like this.
List<Person> filteredPersons = allPersons.FindAll(FilterPersons);
Below is the a sample method you would use as your filter.
bool FilterPersons(Person p)
{
if(//enter criteria here to determine if you want to select the person)
return true;
else
return false;
}
To do what you want this may be the code you need.
bool FilterPersons(Person p)
{
var itemAge = Common.GetAge(item.BirthDay);
if( itemAge > AgeFrom )
return true;
else
return false;
}
Assuming you can apply filters on the result:
You can apply normal filters ( in linq expressions ) and than apply your functions on the result. Of course, you need to refactor your methods.
Something like this :
var result= Users.Where(s=>s.Name).ToList();
result= MyFilter(result);
I have a list of objects and I'd like to update a particular member variable within one of the objects. I understand LINQ is designed for query and not meant to update lists of immutable data. What would be the best way to accomplish this? I do not need to use LINQ for the solution if it is not most efficient.
Would creating an Update extension method work? If so how would I go about doing that?
EXAMPLE:
(from trade in CrudeBalancedList
where trade.Date.Month == monthIndex
select trade).Update(
trade => trade.Buy += optionQty);
Although linq is not meant to update lists of immutable data, it is very handy for getting the items that you want to update. I think for you this would be:
(from trade in CrudeBalancedList
where trade.Date.Month == monthIndex
select trade).ToList().ForEach( trade => trade.Buy += optionQty);
I'm not sure if this is the best way, but will allow you to update an element from the list.
The test object:
public class SomeClass {
public int Value { get; set; }
public DateTime Date { get; set; }
}
The extension method:
public static class Extension {
public static void Update<T>(this T item, Action<T> updateAction) {
updateAction(item);
}
}
The test:
public void Test()
{
// test data
List<SomeClass> list = new List<SomeClass>()
{
new SomeClass {Value = 1, Date = DateTime.Now.AddDays(-1)},
new SomeClass {Value = 2, Date = DateTime.Now },
new SomeClass {Value = 3, Date = DateTime.Now.AddDays(1)}
};
// query and update
(from i in list where i.Date.Day.Equals(DateTime.Now.Day) select i).First().Update(v => v.Value += 5);
foreach (SomeClass s in list) {
Console.WriteLine(s.Value);
}
}
So you're expecting to get a single result here. In that case you might consider utilizing the SingleOrDefault method:
var record =
(from trade in CrudeBalancedList
where trade.Date.Month == monthIndex
select trade).SingleOrDefault();
if (record != null)
record.Buy += optionQty;
Note that the SingleOrDefault method expects there to be exactly one or zero value returned (much like a row in a table for some unique primary key). If more than one record is returned, the method will throw an exception.
To create such a method, you would start with its prototype:
public static class UpdateEx {
public void Update(this IEnumerable<T> items,
Expression<Action> updateAction) {
}
}
That's the easy part.
The hard part will be to compile the Expression<Action> into an SQL update statement. Depending on how much syntax you want to support, such a compiler's complexity can range from trivial to impossible.
For an example of compiling Linq Expressions, see the TableQuery class of the sqlite-net project.
I have a List of a "complex" type - an object with a few string properties. The List itself is a property of another object and contains objects of a variety of types, as shown in this abbreviated class structure:
Customer {
public List<Characteristic> Characteristics;
.
.
.
}
Characteristic {
public string CharacteristicType;
public string CharacteristicValue;
}
I'd like to be able to collect a List of the values of a given type of Characteristics for the current Customer, which I can do in a 2-step process as follows:
List<Characteristic> interestCharacteristics = customer.Characteristics.FindAll(
delegate (Characteristic interest) {
return interest.CharacteristicType == "Interest";
}
);
List<string> interests = interestCharacteristics.ConvertAll<string>(
delegate (Characteristic interest) {
return interest.CharacteristicValue;
}
);
That works fine, but it seems like a long way around. I'm sure I must be missing a simpler way of getting to this list, either by chaining together the FindAll() and Convert() methods, or something else I'm overlooking entirely.
For background, I'm working in .Net 2.0, so I'm limited to the .Net 2 generics, and the Characteristic class is an external dependency - I can't change it's structure to simplify it, and there are other aspects of the class that are important, just not in relations to this problem.
Any pointers or additional reading welcomed.
Here's a generator implementation
public static IEnumerable<string> GetInterests(Customer customer)
{
foreach (Characteristic c in customer.Characteristics)
{
if (c.CharacteristicType == "Interest")
yield return c.CharacteristicValue;
}
}
sadly 3.5 extension methods and lambda are out based on your requirements but for reference here's how to do it:
customer.Characteristics
.Where(c => c.CharacteristicType == "Interest")
.Select(c => c. CharacteristicValue);
I would do some of the work manualy. By doing a FindAll first, and then a Convert, you're looping through your collection twice. It doesn't seem neccessary. If all you want at the end of the day, is a List of CharacteristicValue then just loop through your original collection, and add the CharacteristicValue to a List of each one that matches your criteria. Something like this:
Predicate<Characteristic> criteria = delegate (Characteristic interest)
{
return interest.CharacteristicType == "Interest";
};
List<string> myList = new List<string>();
foreach(Characteristic c in customer.Characteristics)
{
if(criteria(c))
{
myList.Add(c.CharacteristicValue);
}
}
Why not create a Dictionary<string, List<string>>, that way you can add "Interest" as the key, and a list of values as the value. For example:
Customer {
public Dictionary<string, List<string>> Characteristics;
.
.
.
}
...
Characteristics.Add("Interest", new List<string>());
Characteristics["Interest"].Add("Post questions on StackOverflow");
Characteristics["Interest"].Add("Answer questions on StackOverflow");
..
List<Characteristic> interestCharacteristics = Characteristics["Interest"];
Furthermore, if you wanted, you could limit your characteristics to a list of possible values by making it an enum, then use that as the data type of your dictionary's key:
public enum CharacteristicType
{
Interest,
Job,
ThingsYouHate
//...etc
}
then declare your dictionary as:
public Dictionary<CharacteristicType, List<string>> Characteristics;
..
Characteristics.Add(CharacteristicType.Interest, new List<string>());
Characteristics[CharacteristicType.Interest].Add("Post questions on StackOverflow");
Characteristics[CharacteristicType.Interest].Add("Answer questions on StackOverflow");