I have a List<MyClass> and I want to sort it by DateTime CreateDate attribute of MyClass.
Is that possible with LINQ ?
Thanks
To sort the existing list:
list.Sort((x,y) => x.CreateDate.CompareTo(y.CreateDate));
It is also possible to write a Sort extension method, allowing:
list.Sort(x => x.CreateDate);
for example:
public static class ListExt {
public static void Sort<TSource, TValue>(
this List<TSource> list,
Func<TSource, TValue> selector) {
if (list == null) throw new ArgumentNullException("list");
if (selector == null) throw new ArgumentNullException("selector");
var comparer = Comparer<TValue>.Default;
list.Sort((x,y) => comparer.Compare(selector(x), selector(y)));
}
}
You can enumerate it in sorted order:
IEnumerable<MyClass> result = list.OrderBy(element => element.CreateDate);
You can also use ToList() to convert to a new list and reassign to the original variable:
list = list.OrderBy(element => element.CreateDate).ToList();
This isn't quite the same as sorting the original list because if anyone still has a reference to the old list they won't see the new ordering. If you actually want to sort the original list then you need to use the List<T>.Sort method.
Here is a sample:
using System.Collections.Generic;
using System.Linq;
namespace Demo
{
public class Test
{
public void SortTest()
{
var myList = new List<Item> { new Item { Name = "Test", Id = 1, CreateDate = DateTime.Now.AddYears(-1) }, new Item { Name = "Other", Id = 1, CreateDate = DateTime.Now.AddYears(-2) } };
var result = myList.OrderBy(x => x.CreateDate);
}
}
public class Item
{
public string Name { get; set; }
public int Id { get; set; }
public DateTime CreateDate { get; set; }
}
}
Sure the other answers with .OrderBy() work, but wouldn't you rather make your source item inherit from IComparable and just call .Sort()?
class T {
public DateTime CreatedDate { get; set; }
}
to use:
List<T> ts = new List<T>();
ts.Add(new T { CreatedDate = DateTime.Now });
ts.Add(new T { CreatedDate = DateTime.Now });
ts.Sort((x,y) => DateTime.Compare(x.CreatedDate, y.CreatedDate));
Related
I'm trying find all items in my database that have at least one value in an array that matches any value in an array that I have in my code (the intersection of the two arrays should not be empty).
Basically, I'm trying to achieve this :
public List<Book> ListBooks(string partitionKey, List<string> categories)
{
return _client.CreateDocumentQuery<Book>(GetCollectionUri(), new FeedOptions
{
PartitionKey = new PartitionKey(partitionKey)
})
.Where(b => b.Categories.Any(c => categories.Contains(c))
.ToList();
}
With the Book class looking like this :
public class Book
{
public string id {get;set;}
public string Title {get;set;}
public string AuthorName {get;set;}
public List<string> Categories {get;set;}
}
However the SDK throws an exception saying that Method 'Any' is not supported when executing this code.
This doesn't work either :
return _client.CreateDocumentQuery<Book>(GetCollectionUri(), new FeedOptions
{
PartitionKey = new PartitionKey(partitionKey)
})
.Where(b => categories.Any(c => b.Categories.Contains(c))
.ToList();
The following code works because there's only one category to find :
public List<Book> ListBooksAsync(string category)
{
return _client.CreateDocumentQuery<Book>(GetCollectionUri())
.Where(b => b.Categories.Contains(category))
.ToList();
}
In plain SQL, I can queue multiple ARRAY_CONTAINS with several OR the query executes correctly.
SELECT * FROM root
WHERE ARRAY_CONTAINS(root["Categories"], 'Humor')
OR ARRAY_CONTAINS(root["Categories"], 'Fantasy')
OR ARRAY_CONTAINS(root["Categories"], 'Legend')
I'm trying to find the best way to achieve this with LINQ, but I'm not even sure it's possible.
In this situation I've used a helper method to combine expressions in a way that evaluates to SQL like in your final example. The helper method 'MakeOrExpression' below lets you pass a number of predicates (in your case the individual checks for b.Categories.Contains(category)) and produces a single expression you can put in the argument to .Where(expression) on your document query.
class Program
{
private class Book
{
public string id { get; set; }
public string Title { get; set; }
public string AuthorName { get; set; }
public List<string> Categories { get; set; }
}
static void Main(string[] args)
{
var comparison = new[] { "a", "b", "c" };
var target = new Book[] {
new Book { id = "book1", Categories = new List<string> { "b", "z" } },
new Book { id = "book2", Categories = new List<string> { "s", "t" } },
new Book { id = "book3", Categories = new List<string> { "z", "a" } } };
var results = target.AsQueryable()
.Where(MakeOrExpression(comparison.Select(x => (Expression<Func<Book, bool>>)(y => y.Categories.Contains(x))).ToArray()));
foreach (var result in results)
{
// Should be book1 and book3
Console.WriteLine(result.id);
}
Console.ReadLine();
}
private static Expression<Func<T,bool>> MakeOrExpression<T>(params Expression<Func<T,bool>>[] inputExpressions)
{
var combinedExpression = inputExpressions.Skip(1).Aggregate(
inputExpressions[0].Body,
(agg, x) => Expression.OrElse(agg, x.Body));
var parameterExpression = Expression.Parameter(typeof(T));
var replaceParameterVisitor = new ReplaceParameterVisitor(parameterExpression,
Enumerable.SelectMany(inputExpressions, ((Expression<Func<T, bool>> x) => x.Parameters)));
var mergedExpression = replaceParameterVisitor.Visit(combinedExpression);
var result = Expression.Lambda<Func<T, bool>>(mergedExpression, parameterExpression);
return result;
}
private class ReplaceParameterVisitor : ExpressionVisitor
{
private readonly IEnumerable<ParameterExpression> targetParameterExpressions;
private readonly ParameterExpression parameterExpression;
public ReplaceParameterVisitor(ParameterExpression parameterExpressionParam, IEnumerable<ParameterExpression> targetParameterExpressionsParam)
{
this.parameterExpression = parameterExpressionParam;
this.targetParameterExpressions = targetParameterExpressionsParam;
}
public override Expression Visit(Expression node)
=> targetParameterExpressions.Contains(node) ? this.parameterExpression : base.Visit(node);
}
}
If I have a class like this
`
class Person
{
public string First;
public string Last;
public bool IsMarried;
public int Age;
}`
Then how can I write a LINQ Expression where I could select properties of a Person. I want to do something like this (user can enter 1..n properties)
SelectData<Person>(x=>x.First, x.Last,x.Age);
What would be the input expression of my SelectData function ?
SelectData(Expression<Func<TEntity, List<string>>> selector); ?
EDIT
In my SelectData function I want to extract property names and then generate SELECT clause of my SQL Query dynamically.
SOLUTION
Ok, so what I have done is to have my SelectData as
public IEnumerable<TEntity> SelectData(Expression<Func<TEntity, object>> expression)
{
NewExpression body = (NewExpression)expression.Body;
List<string> columns = new List<string>();
foreach(var arg in body.Arguments)
{
var exp = (MemberExpression)arg;
columns.Add(exp.Member.Name);
}
//build query
And to use it I call it like this
ccc<Person>().SelectData(x => new { x.First, x.Last, x.Age });
Hopefully it would help someone who is looking :)
Thanks,
IY
I think it would be better to use delegates instead of Reflection. Apart from the fact that delegates will be faster, the compiler will complain if you try to fetch property values that do not exist. With reflection you won't find errors until run time.
Luckily there is already something like that. it is implemented as an extension function of IEnumerable, and it is called Select (irony intended)
I think you want something like this:
I have a sequence of Persons, and I want you to create a Linq
statement that returns per Person a new object that contains the
properties First and Last.
Or:
I have a sequence of Persns and I want you to create a Linq statement
that returns per Person a new object that contains Age, IsMarried,
whether it is an adult and to make it difficult: one Property called
Name which is a combination of First and Last
The function SelectData would be something like this:
IEnumerable<TResult> SelectData<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TResult> selector)
{
return source.Select(selector);
}
Usage:
problem 1: return per Person a new object that contains the
properties First and Last.
var result = Persons.SelectData(person => new
{
First = person.First,
Last = person.Last,
});
problem 2: return per Person a new object that contains Age, IsMarried, whether he is an adult and one Property called Name which is a combination
of First and Last
var result = Persons.SelectData(person => new
{
Age = person.Name,
IsMarried = person.IsMarried,
IsAdult = person.Age > 21,
Name = new
{
First = person.First,
Last = person.Last,
},
});
Well let's face it, your SelectData is nothing more than Enumerable.Select
You could of course create a function where you'd let the caller provide a list of properties he wants, but (1) that would limit his possibilities to design the end result and (2) it would be way more typing for him to call the function.
Instead of:
.Select(p => new
{
P1 = p.Property1,
P2 = p.Property2,
}
he would have to type something like
.SelectData(new List<Func<TSource, TResult>()
{
p => p.Property1, // first element of the property list
p -> p.Property2, // second element of the property list
}
You won't be able to name the returned properties, you won't be able to combine several properties into one:
.Select(p => p.First + p.Last)
And what would you gain by it?
Highly discouraged requirement!
You could achive similar result using Reflection and Extension Method
Model:
namespace ConsoleApplication2
{
class Person
{
public string First { get; set; }
public string Last { get; set; }
public bool IsMarried { get; set; }
public int Age { get; set; }
}
}
Service:
using System.Collections.Generic;
using System.Linq;
namespace Test
{
public static class Service
{
public static IQueryable<IQueryable<KeyValuePair<string, object>>> SelectData<T>(this IQueryable<T> queryable, string[] properties)
{
var queryResult = new List<IQueryable<KeyValuePair<string, object>>>();
foreach (T entity in queryable)
{
var entityProperties = new List<KeyValuePair<string, object>>();
foreach (string property in properties)
{
var value = typeof(T).GetProperty(property).GetValue(entity);
var entityProperty = new KeyValuePair<string, object>(property, value);
entityProperties.Add(entityProperty);
}
queryResult.Add(entityProperties.AsQueryable());
}
return queryResult.AsQueryable();
}
}
}
Usage:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Test
{
class Program
{
static void Main(string[] args)
{
var list = new List<Person>()
{
new Person()
{
Age = 18,
First = "test1",
IsMarried = false,
Last = "test2"
},
new Person()
{
Age = 40,
First = "test3",
IsMarried = true,
Last = "test4"
}
};
var queryableList = list.AsQueryable();
string[] properties = { "Age", "Last" };
var result = queryableList.SelectData(properties);
foreach (var element in result)
{
foreach (var property in element)
{
Console.WriteLine($"{property.Key}: {property.Value}");
}
}
Console.ReadKey();
}
}
}
Result:
Age: 18
Last: test2
Age: 40
Last: test4
Background:
I have created a function import which is available in my context object as GetJournalViewItemsQuery()
The function import returns a complex type called JournalViewItem.
Now when I try to load the JournalViewItem into my application DTO called JournalEntry I come up with error:
Error 7 Cannot implicitly convert type 'MyApp.Infrastructure.Models.JournalEntry' to 'MyApp.SqlData.JournalViewItem'
This is the code:
var journalEntry = Context.GetJournalViewItemsQuery()
.Where(i => i.JournalItemId == _journalEntryId)
.Select(x => new JournalEntry(x.JournalItemId,
x.HeaderText,x.JournalText, x.LastUpdatedOn,
x.JournalItemTypeId)).Single();
The error happens at the "new JournalEntry" line.
My question: How can I cast the JournalViewItem complex type to my DTO ?
Thank you
After #JanR suggestion I still have the same problem. The modified code is:
var journalEntry = Context.GetJournalViewItemsQuery()
.Where(i => i.JournalItemId == _journalEntryId)
.Select(x => new JournalEntry
{
JournalEntryNumber = x.JournalItemId,
HeaderText = x.HeaderText,
BodyText = x.JournalText,
LastUpdatedOn = x.LastUpdatedOn,
JournalEntryType = x.JournalItemTypeId
}).Single();
I found out the reason for my problem. I failed to mention (my apologies) that I'm working off generated code from WCF RIA Domain Services for Silverlight application. As such the Context.GetJournalViewItemsQuery() needs to be executed and THEN I can query the results on my callback method using the LINQ expression that #Chuck.Net and JanR have suggested.
Here's the working code to those who might be interested:
public IList<JournalEntryHeader> GetJournalEntryHeaders()
{
PerformQuery<JournalViewItem>(Context.GetJournalViewItemsQuery(), GetJournalEntryHeadersFromDbComplete);
return _journalHeaders;
}
void PerformJournalEntryHeadersQuery(EntityQuery<JournalViewItem> qry,
EventHandler<EntityResultsArgs<JournalViewItem>> evt)
{
Context.Load<JournalViewItem>(qry, r =>
{
if (evt != null)
{
try
{
if (r.HasError)
{
evt(this, new EntityResultsArgs<JournalViewItem>(r.Error));
}
else if (r.Entities.Count() > 0)
{
evt(this, new EntityResultsArgs<JournalViewItem>(Context.JournalViewItems));
}
else if (r.Entities.Count() == 0 && _currentJournalItemsPage > 0)
{
GetPrevPageJournalEntryHeadersAsync();
}
}
catch (Exception ex)
{
evt(this, new EntityResultsArgs<JournalViewItem>(ex));
}
}
}, null);
}
void GetJournalEntryHeadersFromDbComplete(object sender, EntityResultsArgs<JournalViewItem> e)
{
if (e.Error != null)
{
string errMsg = e.Error.Message;
}
else
{
_journalHeaders = e.Results
.Select(
x => new JournalEntryHeader(x.JournalItemId,
x.ProjectName,
x.TopicName,
x.HeaderText,
x.EntryTypeName,
x.LastUpdatedOn)).ToList();
GetJournalEntryHeadersComplete(this, new JournalEntryHeaderItemsEventArgs(_journalHeaders));
}
}
What you need to do is the following, in the new JournalEntry() function you will need to set all the properties to the JournalViewItem object.
var journalEntry = Context.GetJournalViewItemsQuery()
.Where(i => i.JournalItemId == _journalEntryId)
.Select(x => new JournalEntry {
JournalEntryId = x.JournalItemId,
HeaderText = x.HeaderText,
JournalText = x.JournalText
//etc
}).Single();
I am just guessing the actual property names here as I am not familiar with what the JounralEntry object looks like.
EDIT: added {}
I created a ConsoleApp to test #JanR answer. It seems to be working correctly.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StackOverFlowConsoleApplication
{
class Program
{
static void Main(string[] args)
{
List<JournalViewItem> JournalViewItems = new List<JournalViewItem>()
{
new JournalViewItem(){JournalItemId =1, HeaderText="HeaderText", JournalText="JournalText", LastUpdatedOn= DateTime.Today, JournalItemTypeId=1},
};
int _journalEntryId = 1;
var journalEntry = JournalViewItems
.Where(i => i.JournalItemId == _journalEntryId)
.Select(x => new JournalEntry
{
JournalEntryNumber = x.JournalItemId,
HeaderText = x.HeaderText,
BodyText = x.JournalText,
LastUpdatedOn = x.LastUpdatedOn,
JournalEntryType = x.JournalItemTypeId
}).Single();
}
class JournalViewItem
{
public int JournalItemId { get; set; }
public string HeaderText { get; set; }
public string JournalText { get; set; }
public DateTime LastUpdatedOn { get; set; }
public int JournalItemTypeId { get; set; }
}
class JournalEntry
{
public int JournalEntryNumber { get; set; }
public string HeaderText { get; set; }
public string BodyText { get; set; }
public DateTime LastUpdatedOn { get; set; }
public int JournalEntryType { get; set; }
}
}
}
I looked into complx type a little bit. I tried the following code in my own project and was able to reproduce the error you mentioned:
var result = (from y in CS.PSMBIPMTTXLOGs select new PSMBIPMTCONTROL(){MBI_PMT_TX_ID = y.MBI_PMT_TX_ID}).ToList();
But when I changed it to return anonymous type, it worked:
var result = (from y in CS.PSMBIPMTTXLOGs select new {MBI_PMT_TX_ID = y.MBI_PMT_TX_ID}).ToList();
You mentioned that you have even tried creating an anonymous type instead of newing it into my DTO, but it still complains. Can you post your code for returning an anonymous type and the error message it gives? Thank you.
I found out the reason for my problem. I failed to mention (my apologies) that I'm working off generated code from WCF RIA Domain Services for Silverlight application. As such the Context.GetJournalViewItemsQuery() needs to be executed and THEN I can query the results on my callback method using the LINQ expression that #Chuck.Net and JanR have suggested.
You'll find the answer in the original post where I entered the question.
I used generic ORM list
example.
This is my table.
Person table
Guid
Name
LastName
and this is my struct class.
public struct PersonItem // database and class field names are the same
{
public Guid Guid{ get; set; }
public string Name { get; set; }
public string LastName { get; set; }
}
public struct PersonItems
{
public PersonItems()
{
Items = new List<PersonItem>();
}
public List<PersonItem> Items { get; set; }
}
I'm using such and no problem but I always have to write field's
public PersonItems GetPersons()
{
var query = (from p in _DbEntities.t_Crew
select p).ToList();
if (query != null)
{
foreach (var item in query)
{
_PersonItems.Items.Add(new PersonItem
{
Guid = item.Guid,
Name = item.Name,
LastName = item.LastName
});
}
}
return _PersonItems;
}
public PersonItems GetPersons()
{
PersonItems personItems = new PersonItems();
var query = from p in _DbEntities.t_Person
select p; >> this here query I need to convert linq query result to list
personItems = query.ToList();
return personItems ;
}
error
Cannot implicitly convert type System.Collections.Generic.List' to PersonData.PersonItems'.
Try this
public PersonItems GetPersons()
{
PersonItems personItems = new PersonItems();
var query = (from p in _DbEntities.t_Person
select new PersonItems
{
test = p.SomeName,
//Other Stuff
//...
}).ToList();
return query;
}
Comparing to the other version of your GetPersons() method, I think this line :
personItems = query.ToList();
should've been this way instead :
personItems.Items = query.ToList();
Update regarding the latest error. You can't assign list of t_Person to Item property which is of type list of PersonItem. Hence, your query need to be adjusted to return list of PersonItem :
var query = from p in _DbEntities.t_Person
select new PersonItem
{
Guid = p.Guid,
Name = p.Name,
LastName = p.LastName
};
or other option is to change definition of Item property to list of t_Person :
public List<t_Person> Items { get; set; }
Cannot implicitly convert type System.Collections.Generic.List' to PersonData.PersonItems'
Above error says that you are trying to convert generic list to PersonItems.
So at this line, query.ToList() code returns List<PersonItem> not the PersonItems
var query = from p in _DbEntities.t_Person
select p; >> this here query I need to convert linq query result to list
personItems = query.ToList();
return personItems ;
So thats the reason above line of code fails.
What about this?
Change .ctor of PersonItems:
public struct PersonItems
{
public PersonItems(List<PersonItem> items)
{
Items = items;
}
public List<PersonItem> Items { get; set; }
}
And then method GetPersons():
public PersonItems GetPersons()
{
return new PersonItems(_DbEntities.t_Person.ToList());
}
For simplicity sake lets assume I have the following two classes:
public class ComplexClass
{
public List<SubClass> SubClasses { get; set; }
public string Name { get; set; }
}
public class SubClass
{
public string Name { get; set; }
}
I have a List<ComplexClass> and I need to build a query based on some parameters.
It's an easy task if all I need to do is use the Name property of ComplexClass. Here's an example:
static IQueryable<ComplexClass> GetQuery(string someParameter, string someOtherParameter)
{
var query = list.AsQueryable();
if (!String.IsNullOrEmpty(someParameter))
query = query.Where(c => c.Name.StartsWith(someParameter));
if (!String.IsNullOrEmpty(someOtherParameter))
query = query.Where(c => c.Name.EndsWith(someOtherParameter));
return query;
}
Based on the parameters I have I can add more query elements. Of course the above example is simple, but the actual problem contains more parameters, and that number can grow.
Things aren't as simple if I want to find those ComplexClass instances which have SubClass instances which meet criteria based on parameters:
static IQueryable<ComplexClass> GetSubQuery(string someParameter, string someOtherParameter)
{
var query = list.AsQueryable();
if (!String.IsNullOrEmpty(someParameter))
if (!String.IsNullOrEmpty(someOtherParameter))
return query.Where(c => c.SubClasses.Where(sc => sc.Name.StartsWith(someParameter) && sc.Name.EndsWith(someOtherParameter)).Any());
else
return query.Where(c => c.SubClasses.Where(sc => sc.Name.StartsWith(someParameter)).Any());
else
if (!String.IsNullOrEmpty(someOtherParameter))
return query.Where(c => c.SubClasses.Where(sc => sc.Name.EndsWith(someOtherParameter)).Any());
else
return null;
}
I can no longer just add bits of the query based on each parameter, I now need to write the whole query in one go, and this means I need to check every combination of parameters, which is hardly ideal.
I suspect the key is to build an Expression class and create a lambda expression from that, but I'm not sure how to tackle the problem.
Any suggestions? :)
EDIT:
My initial idea was this:
static IQueryable<ComplexClass> GetSubQuery(string someParameter, string someOtherParameter)
{
var query = list.AsQueryable();
query = query.Where(c =>
{
var subQuery = c.SubClasses.AsQueryable();
if (!String.IsNullOrEmpty(someParameter))
subQuery = subQuery.Where(sc => sc.Name.StartsWith(someParameter));
if (!String.IsNullOrEmpty(someOtherParameter))
subQuery = subQuery.Where(sc => sc.Name.EndsWith(someOtherParameter));
return subQuery.Any();
});
return query;
}
This works in my small console test application as it's using LINQ to Objects. Unfortunately, I need to use Entity Framework and LINQ to Entities, which causes an implementation similar to the one above to throw a A lambda expression with a statement body cannot be converted to an expression tree error message.
I'm assuming that in you real-life code the SubClasses property is IQueryable<SubClass> rather than List<SubClass>?
If so, then your query building becomes easy:
static IQueryable<ComplexClass> GetSubQuery(
string someParameter, string someOtherParameter)
{
var query = list.AsQueryable();
if (!String.IsNullOrEmpty(someParameter))
query = query.Where(c => c.SubClasses
.Where(sc => sc.Name.StartsWith(someParameter)).Any());
if (!String.IsNullOrEmpty(someOtherParameter))
query = query.Where(c => c.SubClasses
.Where(sc => sc.Name.StartsWith(someOtherParameter)).Any());
return query;
}
Mixing IEnumerable<T> and IQueryable<T> using AsQueryable() is never a good idea.
I implemented my solution in a simple Console Project:
internal class Program
{
#region Constants and Fields
private static readonly List<ComplexClass> list = new List<ComplexClass>
{
new ComplexClass
{
Name = "complex",
SubClasses = new List<SubClass>
{
new SubClass
{
SubName = "foobar"
}
}
},
new ComplexClass
{
Name = "complex",
SubClasses = new List<SubClass>
{
new SubClass
{
SubName = "notfoobar"
}
}
}
};
#endregion
#region Public Methods
public static void Main(string[] args)
{
Console.WriteLine("foo / bar :");
GetSubQuery("foo", "bar");
Console.WriteLine();
Console.WriteLine("foo / null :");
GetSubQuery("foo", null);
Console.WriteLine();
Console.WriteLine("string.Empty / bar :");
GetSubQuery(string.Empty, "bar");
Console.WriteLine();
Console.WriteLine("maeh / bar :");
GetSubQuery("maeh", "bar");
Console.ReadKey();
}
#endregion
#region Methods
private static void GetSubQuery(string startsWith,
string endsWith)
{
var query = from item in list
let StartIsNull = string.IsNullOrEmpty(startsWith)
let EndIsNull = string.IsNullOrEmpty(endsWith)
where
(StartIsNull || item.SubClasses.Any(sc => sc.SubName.StartsWith(startsWith)))
&& (EndIsNull || item.SubClasses.Any(sc => sc.SubName.EndsWith(endsWith)))
select item;
foreach (var complexClass in query)
{
Console.WriteLine(complexClass.SubClasses.First().SubName);
}
}
#endregion
public class ComplexClass
{
#region Public Properties
public string Name { get; set; }
public List<SubClass> SubClasses { get; set; }
#endregion
}
public class SubClass
{
#region Public Properties
public string SubName { get; set; }
#endregion
}
}
The Console Output is:
foo / bar :
foobar
foo / null :
foobar
string.Empty / bar :
foobar
notfoobar
maeh / bar :