I'm trying to use OrderBy for a nested property but I can't get it to work.
Models:
public class TPRenewalCycle
{
public virtual ICollection<TPCaseEvent> CaseEvents { get; set; }
}
public class TPCaseEvent
{
public DateTime? DueDate { get; set; }
}
Method:
List<TPRenewalCycle> cycles = renewalCycles
var nextRenewalCycle = cycles.OrderBy(cycle => cycle.CaseEvents.OrderBy(caseEvent => caseEvent.DueDate)).FirstOrDefault();
This gives me the runtime error:
At least one object must implement IComparable.
Is this due to the nullable DateTime or CaseEvents? How can I solve this?
In T-SQL I can do this:
SELECT CE.DueDate
FROM TPRenewalCycles RC
INNER JOIN TPCaseEvents CE on (CE.BusinessSystemId = RC.BusinessSystemId and CE.CaseId = RC.CaseId and CE.Action = RC.Action and CE.Cycle = RC.Cycle)
Order by CE.DueDate
Since OrderBy expression needs to supply a value to be used as the comparison key for the entire record, you need to select the earliest due date in the list:
var nextRenewalCycle = cycles
.OrderBy(cycle => cycle.CaseEvents.Select(caseEvent => caseEvent.DueDate).Min())
.FirstOrDefault();
If you are looking for the earliest date, as in your SQL query, you could use SelectMany instead:
var nextRenewalCycle = cycles
.SelectMany(cycle => cycle.CaseEvents)
.Select(caseEvent => caseEvent.DueDate)
.Min();
Related
I am trying to find a way to reuse predicates for filtering entities in EF 6.1.3. I've run into a problem filtering related properties using 'Where'.
E.g. if I have this interface IValidFromTo
public interface IValidFromTo
{
DateTime StartDate { get; set;}
DateTime EndDate { get; set; }
}
and a function that returns a predicate for Where :
public class Extensions
{
public static Expression<Func<T, bool>> Current<T>()
where T : IValidFromTo
{
var currentDate = DateTime.Now;
return x => x.StartDate <= currentDate && x.EndDate >= currentDate;
}
}
See http://www.albahari.com/nutshell/predicatebuilder.aspx for background.
When applied directly to DbSet, this method works.
var query = ctx.Items.Where(Extensions.Current<Item>()); // compiles
But how to make it work with a more complex query dealing with navigation properties?
E.g. if I have a DbSet<Person> with a collection of Item:
public class Person
{
...
public virtual ICollection<Item> Items { get; set; }
}
and I want to project it into an object containing the name of the person and just the current Items, I end up with some rather cluttered code:
var relationQuery = ctx.People.Select(x => new
{ Name = x.Name,
CurrentItems = x.Items.AsQueryable().Where(Extensions.Current<Item>())
});
I wonder if it is possible to improve this code, e.g. to be able to write something like
CurrentItems = x.Items.Current() // quasi an extension method on `ICollection<Item>`?
(writing an extension method on ICollection<IValidFromTo> doesn't work, because EFf wouldn't recognize this method and throw an error)
UPDATE
Seems like this is achievable via a Join (supposing that each Person can only have a single valid item):
var isCurrent= x => <<some condition on x>>;
...
var validItems = ctx.Items.Where(isCurrent);
var peopleWithCurrentItems = from person in ctx.Persons
join item in validItems on person.Id equals item.Owner.Id
select new { Person = person, Item = item };
If there may be more than one valid Item per Person, then
var grouped = peopleWithValid.GroupBy(x => x.Person);
However, this version of the query will exclude persons with no matching Items.
So I am new to C#, LINQ, and MVC. I am trying to get a list of Ages, but it says
The specified type member 'Age' is not supported in LINQ to Entities.
Only initializers, entity members, and entity navigation properties
are supported.
For a previous tutorial, they use this exact same logic, except they check a string, not an int (Age). Why is this giving me a fit, and how can I fix it?
public ActionResult SearchIndex(string ageValue, string searchString)
{
if (!string.IsNullOrEmpty(ageValue))
{
var AgeList = new List<string>();
var AgeListQry = from d in db.Actors orderby d.Age select d.Age.ToString();
AgeList.AddRange(AgeListQry.Distinct());
}
// other stuff
}
I want to learn what is going on, so that I can avoid this in the future!
Entity Model code
public class Actor
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime BirthDate { get; set; }
public int Age
{
get {
return (int)(DateTime.Now - BirthDate).TotalDays / 365;
}
}
public decimal NetValue { get; set; }
}
public class ActorDBContext : DbContext
{
public DbSet<Actor> Actors { get; set; }
}
As mentioned in the comments, you can't call ToString() in a Linq to Entities query. Instead do it like this:
var AgeList = new List<string>();
//retrieve as whatever type Age is, no conversion in SQL Server
var AgeListQry = (from d in db.Actors orderby d.Age select d.Age).ToList();
//convert them after the fact, using Linq to Objects
AgeList.AddRange(AgeListQry.Select(a => a.ToString()).Distinct());
EDIT
I saw your latest update that does show that Age is not a database column. You are then required to do something like this (assuming BirthDate is properly mapped):
var AgeList = new List<string>();
//retrieve BirthDate from SQL Server and use ToList() to get it to run immediately
var AgeListQry = (from d in db.Actors orderby d.BirthDate select d.BirthDate).ToList();
//convert them after the fact, using Linq to Objects
AgeList.AddRange(AgeListQry.Select(bd => ((int)(DateTime.Now - bd).TotalDays / 365).ToString()).Distinct());
Linq to Entities maps your expressions to SQL statements and there is nothing for it to map to when you use your Age property. Instead, you need to get what you can from SQL Server (BirthDate) and then do the translation to Age yourself. You could replace the inline code with a method call like this if you'd rather:
AgeList.AddRange(AgeListQry.Select(bd => CalculateAge(bd)).Distinct());
//...
private string CalculateAge(DateTime birthday)
{
return ((int)(DateTime.Now - bd).TotalDays / 365).ToString();
}
You haven't the Age in you DB scheme and it is impossible to convert LINQ to DB query.
You must order the Age collection in client side or add calculated column to your table.
There is another way. Have a converter file, where you pass the object, works with the birthdate and produces the age, returns the same object. That also means, that you can't search the database for the age column
public class ConsumableThreshold
{
public int ThresholdType { get; set; }
public int ManufacturerID { get; set; }
public int ModelID { get; set; }
public int ConsumableVariantID { get; set; }
}
I'm attempting to check two lists of objects for shared properties.
I will need to check various other properties depending on the results of previous matches.
For example if the ThresholdType matches, I then need to check a second property and if that matches I need to check the ModelID.
I have this query, which effectively does what I want but there are problems with it mainly that further down I drill the more the readability is going to be reduced.
var query= existingThresholds.Where(
e => defaultThresholds.Any(
d => d.ThresholdType == e.ThresholdType)).Where(
e => defaultThresholds.Any(
d => d.ManufacturerID == e.ManufacturerID)).ToList();
I wanted to do this using join but it does not support the && operator.
var query2 = from e in existingThresholds
join d in defaultThresholdson
e.ThresholdType equals d.ThresholdType &&
e.ManufacturerID equals d.ManufacturerID
select e;
Is there a way to write this as a query without chaining .Where() conditions?
Sure - you're just trying to join on a compound key, which is usually accomplished with an anonymous type:
var query2 = from e in existingThresholds
join d in defaultThresholdson
on new { e.ThresholdType, e.ManufacturerID } equals
new { d.ThresholdType, d.ManufacturerID }
select e;
(It's slightly odd to ignore one half of the join later on, admittedly...)
Is there a way to write this as a query without chaining .Where() conditions?
Yes, use an anonymous type, which has a built-in equality check that compares the values of all properties by name:
var query2 = from e in existingThresholds
join d in defaultThresholds
on new { e.ThresholdType , e.ManufacturerID }
equals new { d.ThresholdType , d.ManufacturerID }
select e;
I wanted to store a collection of expressions accessing object's properties. For example:
class Entity
{
public int Id { get; set; }
public Entity Parent { get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
public decimal Value { get; set; }
public bool Active { get; set; }
}
static void Main(string[] args)
{
var list = new List<Expression<Func<Entity, object>>>();
list.Add(e => e.Id);
list.Add(e => e.Name);
list.Add(e => e.Parent);
list.Add(e => e.Date);
list.Add(e => e.Value);
list.Add(e => e.Active);
StringBuilder b = new StringBuilder();
list.ForEach(f => b.AppendLine(f.ToString()));
Console.WriteLine(b.ToString());
Console.ReadLine();
}
This code outputs:
e => Convert(e.Id)
e => e.Name
e => e.Parent
e => Convert(e.Date)
e => Convert(e.Value)
e => Convert(e.Active)
It does add Convert to value types.
As far as in the end I wanted to use those expressions with LINQ to SQL, I need not to have that Convert in expressions, for them to be successfully translated to SQL.
How can I achieve this?
P.S.: expressions from this collection are later used as arguments to OrderBy and ThenBy methods.
If you create a function generic in the proeprty type you can avoid the Convert:
private static LambdaExpression GetExpression<TProp>
(Expression<Func<Entity, TProp>> expr)
{
return expr;
}
then you can change the type of list:
var list = new List<LambdaExpression>();
list.Add(GetExpression(e => e.Id));
list.Add(GetExpression(e => e.Name));
This will require you to create your OrderBy and ThenBy expressions using reflection e.g.
LambdaExpression idExpr = list[0];
Type keyType = idExpr.ReturnType;
var orderByMethod = typeof(Queryable).GetMethods()
.Single(m => m.Name == "OrderBy" && m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(Entity), keyType);
var ordered = (IQueryable<Entity>)
orderByMethod.Invoke(null, new object[] { source, idExpr });
I patched up a EF code first attempt at using your code like this
public class Entity
{
public int Id { get; set; }
public Entity Parent { get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
public decimal Value { get; set; }
public bool Active { get; set; }
}
public class EntityContext : DbContext
{
public EntityContext()
: base(new SqlCeConnection("Data Source=Database.sdf;Persist Security Info=False;"),
contextOwnsConnection: true)
{
// Using a SQL Compact database as backend
}
public DbSet<Entity> Entities { get; set; }
}
and attempted some linq on the context
static void Main(string[] args)
{
var list = new List<Expression<Func<Entity, object>>>();
list.Add(e => e.Date);
list.Add(e => e.Name);
using (var c = new EntityContext())
{
//each time a new record is added
var data = new Entity
{
Name = string.Format("Data{0}", c.Entities.Count()),
Date = DateTime.Now
};
c.Entities.Add(data);
c.SaveChanges();
// sort by date
foreach (var e in c.Entities.OrderBy(list.First().Compile()))
Console.WriteLine(string.Format("{0} - {1}", e.Name, e.Date));
// sort by name .. in reverse
foreach (var e in c.Entities.OrderByDescending(list.Last().Compile()))
Console.WriteLine(string.Format("{0} - {1}", e.Name, e.Date));
}
Console.ReadLine();
}
There were no issues running the code.
UPDATE The same holds true for LINQ to SQL: I built a table in a local SQL Server with the same structure as the class, and tried to OrderBy it : no problem.
My answer is "You don't need to worry about that".
Thank's to the answer by Alex I found out for myself that, when ordering the data I can use two different methods, depending on the specified argument:
Queryable.OrderBy Method with Expression<Func<TSource, TKey>>
Enumerable.OrderBy Method with Func<TSource, TKey>
When Queryable.OrderBy is used, LINQ compiles the OrderBy clause into the SQL statement, executed over the database. So when I try to give it a Expression<Func<TEntity, object>> that looks like e => Convert(e.Field), LINQ throws an InvalidOperationException, saying Cannot order by type 'System.Object'.
When Enumerable.OrderBy is used, LINQ does not compile the OrderBy clause into the SQL query, but executes the current query and applies sorting on the enumerable of entities, returned by the query, in the program's memory. Here no problem with ordering by Func<TEntity, object>.
So I found two alternatives here:
Query the database without sorting and order the returned result set
Provide better expressions to LINQ, that it could compile the SQL query, and then apply sorting in the database layer; here the answer by Lee suggests one way..
In my exact case sorting is the last operation to execute, and I don't see much harm, if I order the result set in the programm's memory...I'm not going to expect huge amounts of data to be returned...
Though in a more common case, probably it's still better to do all possible operations in the database layer...
P.S.: SO: Order a linq query - a close discussion...
I am a beginner with LINQ and lambda function's. I was wondering how can i convert multiple linq statement to one statement using selectmany function.
string viewname = (from a in StoredProcedureParameters.Tables[0].AsEnumerable()
where a.Field<string>("ViewName") == displayName
select a.Field<string>("View")).Single();
DateTime date = (from d in StoredProcedureParameters.Tables[0].AsEnumerable()
select d.Field<DateTime>("Date")).First();
Thanks for help in advance.
If you want a single query, you could have the select value be an anonymous type. But the scope of the return value is the method where the type is created.
var result = (from a in StoredProcedureParameters.Tables[0].AsEnumerable()
where a.Field<string>("ViewName") == displayName
select new { View = a.Field<string>("View"),
Date = d.Field<DateTime>("Date") }).Single();
then the variable result will be an object with the properties 'View' and 'Date'. You can't return this value from a method though. If you need to return the value, you could create a simple class
public class ViewDate
{
public string View { get; set; }
public DateTime Date { get; set; }
}
Then using an object initializer, you will end up with result containing an instance of the ViewDate object, which you can return from your method.
var result = (from a in StoredProcedureParameters.Tables[0].AsEnumerable()
where a.Field<string>("ViewName") == displayName
select new ViewDate() { View = a.Field<string>("View"),
Date = d.Field<DateTime>("Date") }).Single();
I don't think SelectMany does what you think it does. It's usually just a flatten operation. Why do you think it would help you in this case? Why do you want to avoid two queries?