Get the lowest year from LINQ query results - c#

I am trying to find the earliest year from a group of rows returned by a LINQ query. The ActivationDate is stored as a datetime value in the DB.
I know that I can make a separate query to get just the date, but I would rather use the results from the existing query, as it is used for several other things.
IEnumerable<MonitoringPanel> panels = from rows in db.MonitoringPanels
where rows.DealerEntity == dealerIDint
select rows;
However this keeps throwing an error:
var testDate = panels.Min().ActivationDate;
Error:
System.ArgumentException was unhandled by user code.
It will throw an error even if I try to select the lowest PanelNumner (stored as an int), but the following does work:
var testDate = panels.FirstOrDefault().ActivationDate;
Solution
DateTime testDate = (DateTime)panels.Min( thing => thing.ActivationDate);
int lowYear = testDate.Year;

Unless the Enumerable is of a perimative type you need to add a lambda expression to tell it what property of the class to use. Try
var testDate = panels.Min(x => x.ActivationDate);

I think you need to tell the Min() method what to look for the Minimum of. Which field is the Minimum.
var testDate = panels.Min(panel => panel.ActivationDate);

Related

Exception when comparing dates - Npgsql.PostgresException: '42883'

I'm trying to get records with a date that are the same or later than a given date. But this Exception keeps happening:
Npgsql.PostgresException: '42883: operator does not exist: character varying >= timestamp without time zone'
And here is my code:
var bairro = "test";
var dataBusca = DateTime.Now.AddDays(-7).ToString("yyyy-MM-dd");
var buscaImovel = _context.ImovelModel
.Where(context => context.bairronome == bairro &&
context.datageracaoarq >= DateTime.Parse(dataBusca)).ToList(); //exception happens here
The dates in my database are using the "yyyy-MM-dd" timestamp. What could be the problem?
LINQ could not translate the expression if I casted the field to DateTime in it. The solution was changing the field type in the database to 'date'.

How to perform a date range filter on string with LINQ Query

I am trying to do a date range filter using the following linq:
IQueryable<Movies> movies= _context.Movies
.OrderByDescending(i => i.Id).Select(i => i);
DateTime startDate = Convert.ToDateTime(searchStartDate);
DateTime endDate = Convert.ToDateTime(searchEndDate);
movies = movies.Where(i => Convert.ToDateTime(i.TransDate) >= startDate &&
Convert.ToDateTime(i.TransDate) <= endDate)
.OrderByDescending(j => j.Id);
But it is't working and is giving me InvalidOperationException: The LINQ expression '...' could not be translated.
NOTE: The database I was given to work with has TransDate column in string format('YYYY-MM-DD'), hence this question.
While storing dates as string in database is not a good idea, at least the chosen format is orderable. And while EF Core does not provide translatable method for converting string to date, it allows you to have entity property of type DateTime (as it should have been), and map it to string column in database using value converter. Thus, you would write query against DateTime, and EF Core will convert the constant/parameter values to string and pass them to the SQL query.
Applying it to your case:
Model:
public class Movie
{
// other properties...
public DateTime TransDate { get; set; }
}
Configuration:
const string DateFormat = "yyyy-MM-dd";
modelBuilder.Entity<Movie>().Property(e => e.TransDate)
.HasConversion(
dateValue => dateValue.ToString(DateFormat, CultureInfo.InvariantCulture),
stringValue => DateTime.ParseExact(stringValue, DateFormat, CultureInfo.InvariantCulture)
)
.IsRequired()
.IsUnicode(false) // arbitrary
.HasMaxLength(10); // arbitrary
LINQ query usage:
IQueryable<Movie> movies = ...;
DateTime startDate = ...;
DateTime endDate = ...;
movies = movies
.Where(e => e.TransDate >= startDate && e.TransDate <= endDate);
Try removing one OrderByDescending you already have ordered it in the first statement. You are ordering the same query twice.
To understand the problem, you'll have to be aware of the differences between IEnumerable and IQueryable.
IEnumerable
An object of a class that implements IEnumerable<...> represents a sequence of similar objects. It holds everything to get the first element of the sequence, and as long as you've got an element, you can try to get the next one.
At its lowest level, this is done using GetEnumerator and repeatedly calling MoveNext() / Current, like this:
IEnumerable<Movie> movies = ...
IEnumerator<Movie> enumerator = movies.GetEnumerator();
// try to get the next element:
while (enumerator.MoveNext())
{
// There is a next element
Movie movie = enumerator.Current;
ProcessMovie(movie);
}
Deep inside foreach will do something like this. Every LINQ method that doesn't return IEnumerable<...> will also deep inside call GetEnumerator / MoveNext / Current.
An IEnumerable is meant to be executed by your process, hence the IEnumerable has access to all your procedures.
IQueryable
Although an IQueryable looks like an IEnumerable, it represent the potential to get an enumerable sequence.
For this, the IQueryable has an Expression and a Provider. The Expression holds what data must be fetched in some generic format; the Provider knows where he must fetch the data (Usually a database management system), and what language to use when fetching the data (usually SQL).
As long as you concatenate LINQ methods that return IQueryable<...> the Expression changes. The query is not executed, the database is not contacted. We say that the LINQ method uses deferred execution, or lazy execution. Concatenating such LINQ statements is not expensive.
The LINQ methods that don't return IQueryable<...>, like ToList(), ToDictionary(), FirstOrDefault(), Count(), Any(), are the expensive ones, just like foreach they will deep inside call GetEnumerator().
When you call GetEnumerator(), the Expression is sent to the Provider, who will translate the Expression into SQL and execute the Query. The fetched data is returned as an IEnumerator<...> of which you can call MoveNext() / Current.
Some Providers are smarter than others. For instance, some of them will not fetch the data when you get the Enumerator, but fetch it at the first MoveNext. Others won't fetch all requested data at once, but fetch the data "per page", so if you decide to enumerate only two or three items, then only the first page of Movies are fetched, not all 10000 of them.
But what has this to do with my question?
You use a method that your Provider doesn't know: Convert.ToDateTime. Hence it can't translate it into SQL. In fact, there are several LINQ methods that are not supported by entity framework. See Supported and Unsupported LINQ methods
Your compiler doesn't know how smart the Provider is, so your compiler can't complain. You'll get your error at runtime.
So you can't use Convert.ToDateTime, nor methods like Datetime.Parse.
What to do?
You wrote that the strings that you want to convert are in format 'YYYY-MM-DD'.
What you could do, is use string handling routines to separate them into "YYYY", "MM" and "DD". It depends on your provider whether you can use String.SubString.
Then use class DbFunctions to CreateDateTime
.Where(x => ... && DbFunctions.CreateDateTime(
x.TransDate.SubString(0,4), // YYYY
x.TransDate.SubString(5, 2), // MM
x.TransDate.SubString(8, 2), // DD
0, 0, 0) < endDate;
If your Provider also doesn't accept SubString, search for other methods to extract the YYYY, MM and DD. I know Sqlite has a Date method to convert strings to DateTime.
If you can't find any proper string manipulation routines, consider to convert endDate into format "YYYY-MM-DD" and return compare the TransDate string with the endDateSTring
string endDateString = String.Format("{0:D04}-{1:D02}-{2:D02}", endDate.Year, endDate.Month, ...)
Fiddle a bit with it, until you've got the correct format. Then use:
.Where(x => ... && x.TransDate < endDateString)
If that also does not work, create the SQL query yourself.

LINQ how to return group by as List?

I have a datatable that is populated with 2 columns, DateTime and Data. I have been able to group the list using:
var dates = table.AsEnumerable().GroupBy(x => Convert.ToDateTime(x["DateTime"]).ToShortDateString());
The conversion is to drop the "time" portion of the datetime as I only want 1 date per day so I can iterate over it.
I can see during debugging that the LINQ works out as I intended, but I have almost no experience in LINQ and I do not know how to return these results in a way that is iterable.
Below that LINQ statement I have:
foreach (string date in dates) {
string dummy2 = "";
}
with an error on foreach that says
Cannot convert type 'System.Linq.IGrouping<string, System.Data.DataRow>' to 'string'
The goal here is to return a list of just the unique dates which I can iterate over to perform additional processing/LINQ queries on the datatable
If you just want the dates, then you don't need to use GroupBy, you can use Distinct. Also, you can get them as a DateTime object by just grabbing the Date property, which has a zeroed-out time component:
IEnumerable<DateTime> distinctDates = table.AsEnumerable()
.Select(x => Convert.ToDateTime(x["DateTime"]).Date)
.Distinct();
If you want the groups but are just trying to select the date strings from them, then you want to use the Key property of the groups, which is the property that was used to group the items:
IEnumerable<IGrouping<string, System.Data.DataRow>> dates = table.AsEnumerable()
.GroupBy(x => Convert.ToDateTime(x["DateTime"]).ToShortDateString());
List<string> distinctDates = dates.Select(date => date.Key).ToList();
As a side note, unless you know the data type being returned by something, you might avoid using var, since that was hiding the fact that dates wasn't a collection of strings.
If you want a list of unique dates, you can do something like this.
var dates = table.Select(x => Convert.ToDateTime(x["DateTime"]).ToShortDateString()).Distinct();
that's happening since as the error says you're trying to loop in a dictionary:
Cannot convert type 'System.Linq.IGrouping<string, System.Data.DataRow>' to 'string'
what you can do is add to your GroupBy statement a select portion:
var dates = table.AsEnumerable().GroupBy(x => Convert.ToDateTime(x["DateTime"]).ToShortDateString()).Select(x=> x.date);

Linq - between dates query

I am trying to convert the following sql query as a linq query, however I keep experiencing an error -
Operator '<=' cannot be applied to operands of type 'string' and 'System.DateTime'.
SQL Query:
select top 3 Deal, [property], [event], [Date] from [dbo]. [Database_CRE_Events]
where (convert(datetime,[Date],103) between '01-May-2015' and '15-May-2015') and [property] is not NULL
order by convert(datetime,[Date],103) desc
I believe this is happening because c.Date is a string field from the entity database. I have tried converting the date values to string and datetime to get the following to work, but I keep getting an operand error.
LINQ Query:
DateTime dat = DateTime.Now.AddDays(-10);
string preWeek = dat.ToString("dd-MMM-yyyy");
DateTime dtt = DateTime.Now;
string today = dat.ToString("dd-MMM-yyyy");
var data = db.Database_CRE_Events.Where(c => c.Date <= Convert.ToDateTime(preWeek) && c.property != null)
.Select(x => new Loan() { Name = x.Deal, loan = x.property, evnt = x.Event })
.ToList().Take(3);
return data;
Is it possible to convert the original sql query to a linq query as c.Date being a string parameter?
Thank you for any further assistance.
The problem is that you're introducing strings into the mix for no reason at all. Unless you have to convert a DateTime to or from a string, don't do it.
Your query should be as simple as:
DateTime preWeek = DateTime.Today.AddDays(-10);
var data = db.Database_CRE_Events
.Where(c => c.Date <= preWeek && c.property != null)
.Select(x => new Loan() { Name = x.Deal, loan = x.property, evnt = x.Event })
.ToList()
.Take(3);
return data;
If c.Date is actually a string, you should fix your database so that it isn't a string. It's meant to be a date, so represent it as a date! If you absolutely have to keep it as a string, you should at least use a sortable format, such as yyyy-MM-dd. At that point you could use CompareTo - but it's horrible :(
If the format is dd-MMM-yyyy (as it sounds) you could try performing the parse in the LINQ query, still passing in a DateTime but parsing each value in the database:
.Where(c =>
DateTime.ParseExact(c.Date, "dd-MMM-yyyy", CultureInfo.InvariantCulture) <= preWeek
&& c.property != null)
... but I wouldn't be surprised if that fails. You may want to add a view in SQL which gives a more appropriate version of the data. Fundamentally, if you have to work with a broken schema (in this case using the wrong type and making a poor decision about how to format the data within that type) then you should expect pain. Pass this pain up to managers in order to prioritize changing the schema...
Notes:
You're fetching all the data, and then just taking the first three elements. That's a bad idea. Switch round the calls to ToList and Take after addressing the next bullet...
"First three elements" is only meaningful with ordering. Use OrderBy to specify an ordering
You're not currently using today, so I removed it
If you're only interested in a date, use DateTime.Today
You should carefully consider time zones, both in your database and in your calling code. This is currently using the system default time zone - is that what you want?

Return Timespan ticks difference in LINQ queries

I'm querying a list of objects, and I want to return a TimeSpan's ticks list of the difference between the time registered in the object, and now.
I wanted all in one expression, so:
var list = (from r in res where r.Site == "CNIS"
select Math.Abs((r.Timestamp.Value - DateTime.Now).Ticks)).ToList();
But I get the following error:
Exception Details: DbArithmeticExpression arguments must have a numeric common type
I already managed to do a workaround. For example, my code looks like this now:
var list = new List<long>();
foreach(var item in (from r in res where r.Site == "CNIS" select r))
list.Add(Math.Abs((item.Timestamp.Value - DateTime.Now).Ticks));
But what I really wanted to know is if it is possible to get the Timespan diff from a DateTime value to now, in a single LINQ query
It seems the error is relevant to the translation of your select statement into SQL.If fecthing the results form DB is not a problem you can do it using AsEnumerable and then project the items:
var now = DateTime.Now;
var list = res.Where(r => r.Site == "CNIS")
.AsEnumerable()
.Select(x => Math.Abs((x.Timestamp.Value - now).Ticks))
.ToList();
And since the value of DateTime.Now changes you should probably store the it into a variable and use it in the calculation.

Categories

Resources