Compare Dates using LINQ - asp.net

I have a table, which has columns ItemName, purchaseDate and expiryDate. Essentially what i'm trying to do is write a LINQ query that counts and displays all items that are within 45days or less from their expiry date, comparing them using their purchase date.
So far this is what i've been able to do:
public string stringExpiry { get; set; }
public int intExpiry { get; set; }
intExpiry= _context.GetMyItems.Where(p => p.ExpiryDate <= p.PurchaseDate.AddDays(-45)).Count();
stringExpiry= _context.GetMyItems.Where(p=> p.ExpiryDate<=p.PurchaseDate.AddDays(-45)).ToList();

Let's make this a little bit more generic, so you can use it as any other LINQ method. After that we can specialize in a method for your class
LINQ is defined using extension methods. If you are not familiar how to create an extension method, read Extension Methods Demystified
For the examples I use the following class. Any class with two DateTime properties will do
class PurchasedProduct
{
public string Name {get; set;}
public DateTime PurchaseDate {get; set;}
public DateTime ExpiryDate {get; set;}
}
Our goal is a method that does the following:
TimeSpan maxTime = TimeSpan.FromDays(45);
IEnumerable<PurchardProduct> purchasedProducts = ...
IEnumerable<PurchasedProducts> almostExpiredProducts =
purchasedProducts.WhereAlmostExpired(maxTime);
Here we go!
First a method that says whether a class with two DataTimes is near expiry date:
public static bool IsWithinTimeSpan<T>(this T source,
Func<T, DateTime> startDateSelector,
Func<T, DateTime> endDateSelector,
TimeSpan maxTime)
{
return startDateSelector(source) - endDateSelector(source) < maxTime;
}
In words: take your source. Use the startDateSelector to extract the startDate; Use the endDateSelector to extract the endDate. Subtract these two DateTimes, and return true if the result is less than maxTime;
Usage:
PurchasedProduct purchasedProduct = new PurchasedProduct {...};
bool productNearExpiryDate = product.IsWithinTimeSpan(maxTime);
Well, if we can do this with one T, we can do this with a sequence of T:
public static IEnumerable<T> WhereWithinTimeSpan<T>(
this IEnumerable<T> source,
Func<T, DateTime> startDateSelector,
Func<T, DateTime> expiryDateSelector,
TimeSpan maxTime)
{
return source.Where(t => t.IsWithinTimeSpan(
startDateSelector, expiryDateSelector, maxTime));
}
Well that was easy, only one line of code!
This looks very much like our goal, only one more function to go to make it like the method you want:
public static IEnumerable<PurchasedProduct> WhereAlmostExpired(
this IEnumerable<PurchasedProduct> purchasedProducts,
TimeSpan maxTime)
{
return purchasedProducts.WhereWithinTimeSpan(
product => product.PurchaseDate,
product => product.ExpiryDate,
maxTime);
}
Again, only one line of code! Of course, if your sequence of items is not IEnumerable<PurchasedProduct>, but for instance IEnumerable<MyClass>, change this one line of code accordingly.
So now we are able to use it for your problem:
TimeSpan maxTime = TimeSpan.FromDays(45);
IEnumerable<MyClass> myObjects = ...
IEnumerable<MyClass> almostExpiredObjects = myObjects.WhereAlmostExpired(maxTime);
Simple comme bonjour!
One final remark: if you are not certain that startTime is smaller then endTime, don't forget to use absolute value before your comparison

Related

How to get string[] array from database with EF core [duplicate]

How can I store an array of doubles to database using Entity Framework Code-First with no impact on the existing code and architecture design?
I've looked at Data Annotation and Fluent API, I've also considered converting the double array to a string of bytes and store that byte to the database in it own column.
I cannot access the public double[] Data { get; set; } property with Fluent API, the error message I then get is:
The type double[] must be a non-nullable value type in order to use
it as parameter 'T'.
The class where Data is stored is successfully stored in the database, and the relationships to this class. I'm only missing the Data column.
You can do a thing like this :
[NotMapped]
public double[] Data
{
get
{
string[] tab = this.InternalData.Split(',');
return new double[] { double.Parse(tab[0]), double.Parse(tab[1]) };
}
set
{
this.InternalData = string.Format("{0},{1}", value[0], value[1]);
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
public string InternalData { get; set; }
Thank you all for your inputs, due to your help I was able to track down the best way to solve this. Which is:
public string InternalData { get; set; }
public double[] Data
{
get
{
return Array.ConvertAll(InternalData.Split(';'), Double.Parse);
}
set
{
_data = value;
InternalData = String.Join(";", _data.Select(p => p.ToString()).ToArray());
}
}
Thanks to these stackoverflow posts:
String to Doubles array and
Array of Doubles to a String
I know it is a bit expensive, but you could do this
class Primitive
{
public int PrimitiveId { get; set; }
public double Data { get; set; }
[Required]
public Reference ReferenceClass { get; set; }
}
// This is the class that requires an array of doubles
class Reference
{
// Other EF stuff
// EF-acceptable reference to an 'array' of doubles
public virtual List<Primitive> Data { get; set; }
}
This will now map a single entity (here 'Reference') to a 'list' of your Primitive class. This is basically to allow the SQL database to be happy, and allow you to use your list of data appropriately.
This may not suit your needs, but will be a way to make EF happy.
It would be far easier if you use List<double> rather then double[]. You already have a table that stores your Data values. You probably have foreign key from some table to the table where your double values are stored. Create another model that reflects the table where doubles are stored and add foreign key mappings in the mappings class. That way you will not need to add some complex background logic which retrieves or stores values in a class property.
In my opinion almost all other answers work on the opposite of how it should be.
Entity EF should manage the string and the array must be generated from it. So the array must be whole read and written only when the string is accessed by EF.
A solution involving logic on Data[] is wrong because, as I wrote in a comment, you would run into paradoxical conditions. In all other conditions the variable must remain a pure array.
By putting the "get" and "set" logic in Data[], as I've seen so far, this happens:
1 - Every time an index access is made to the array, the array is automatically recreated from the string. This is a useless work, think of an iteration in a loop...
2 - when you go to set a single element it is not stored because it passes through "get" and not "set".
If you try to declare Data=new []{0,0,0} then set Data[1]=2 , going to re-read Data[1] the result is 0.
My solution is to completely turn the logic around.
public string Data_string
{
get => string.Join(';', Data??Array.Empty());
set => Data= value == null ? Array.Empty<double>() : Array.ConvertAll(value.Split(';',StringSplitOptions.RemoveEmptyEntries), double.Parse);
}
[NotMapped]
public double[] Data {get;set;}
Obviously this only applies to storing and retrieving data on databases, access to Data_string is exclusive to EF.
Once the string is read from the DB it is associated to Data_string which, through set, creates the Data array.
At this point you can work on Data without affecting the string in any way, like a normal array.
When you will ask EF to save in the DB, through the get in the Data_string property, the string will be completely reconstructed based on the Data elements and then stored as a string.
Practically the string is modified only twice, at the moment of reading from the DB and at the moment of saving.
In my opinion this solution is much more efficient than operating continuously on the string.
Nathan White has the best answer (got my vote).
Here is a small improvement over Joffrey Kern's answer to allow lists of any length (untested):
[NotMapped]
public IEnumerable<double> Data
{
get
{
var tab = InternalData.Split(',');
return tab.Select(double.Parse).AsEnumerable();
}
set { InternalData = string.Join(",", value); }
}
[EditorBrowsable(EditorBrowsableState.Never)]
public string InternalData { get; set; }
Don't use double[] use List insted.
Like this.
public class MyModel{
...
public List<MyClass> Data { get; set; }
...
}
public class MyClass{
public int Id { get; set; }
public double Value { get; set; }
}
All that solution that I see there are bad, because:
If you create table, you don't want to store data like this: "99.5,89.65,78.5,15.5" that's not valid! Firstly its a string that means if you can type letter into it and at the moment when your ASP.NET server call double.Parse it will result in FormatException and that you really don't want!
It's slower, because your server must parse the string. Why parse the string instead getting almost ready data from SQL Server to use?
i know this post is Ancient, but in case someone still needs to do something like this, PLEASE DO NOT USE THE ABOVE SOLUTIONS,
as the above solutions are EXTREMELY inefficient (Performance and Disk Space wise).., the best way is to store the array as a Byte array
public byte[] ArrayData;
[NotMapped]
public double[] Array {
get {
var OutputArray = new double[ArrayData.Length / 8];
for (int i = 0;i < ArrayData.Length / 8;i++)
OutputArray[i] = BitConverter.ToDouble(ArrayData, i * 8);
return OutputArray;
}
set {
var OutputData = new byte[value.Length * 8];
for (int i = 0;i < value.Length;i++) {
var BinaryValue = BitConverter.GetBytes(value[i]);
OutputData[(i*8)] = BinaryValue[0];
OutputData[(i*8)+1] = BinaryValue[1];
OutputData[(i*8)+2] = BinaryValue[2];
OutputData[(i*8)+3] = BinaryValue[3];
OutputData[(i*8)+4] = BinaryValue[4];
OutputData[(i*8)+5] = BinaryValue[5];
OutputData[(i*8)+6] = BinaryValue[6];
OutputData[(i*8)+7] = BinaryValue[7];
}
ArrayData = OutputData;
}
}
`
And if you need more performance, you can go for Unsafe code and use pointers .. instead of BitConverter ..
This is way better than saving double values (that can get huge) as string, then spliting the string array !! and then parsing the strings to double !!!
These getter/setters work on the whole array, but if you need to get just one item from the array, you can make a function that gets a single item from the array with a complexity of O(1) :
for Get :
public double Array_GetValue(int Index) {
return BitConverter.ToDouble(ArrayData, Index * 8);
}
for Set :
public void Array_SetValue(int Index, double Value) {
var BinaryValue = BitConverter.GetBytes(Value);
ArrayData[(Index*8)] = BinaryValue[0];
ArrayData[(Index*8)+1] = BinaryValue[1];
ArrayData[(Index*8)+2] = BinaryValue[2];
ArrayData[(Index*8)+3] = BinaryValue[3];
ArrayData[(Index*8)+4] = BinaryValue[4];
ArrayData[(Index*8)+5] = BinaryValue[5];
ArrayData[(Index*8)+6] = BinaryValue[6];
ArrayData[(Index*8)+7] = BinaryValue[7];
}
If your collection can be null or empty, and you want this to be preserved, do this:
[NotMapped]
public double[] Data
{
get => InternalData != null ? Array.ConvertAll(Data.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries), double.Parse) : null;
set => InternalData = value != null ? string.Join(";", value) : null;
}
Also, specify [Column(TypeName = "varchar")] on the string property for a more efficient storage data type.
A perfect enhancement to #Jonas's answer will be to add the necessary annotations. So, a cleaner version would be
[EditorBrowsable(EditorBrowsableState.Never)]
[JsonIgnore]
public string InternalData { get; set; }
[NotMapped]
public double[] Data
{
get => Array.ConvertAll(InternalData.Split(';'), double.Parse);
set
{
InternalData = string.Join(";", value.Select(p => p.ToString(CultureInfo.InvariantCulture)).ToArray());
}
}
The [JsonIgnore] Annotation will ignore the InternalData field from JSON serialization and Swagger UI.
[EditorBrowsable(EditorBrowsableState.Never)] will hide the public method from the IDE IntelliSense

How to use JsonConvert.SerializeObject with formatting to add brackets only around CSV values

I have an object that, when converted to a JSON string using the JsonConvert.SerializeObject method, will look like this:
{"01":{"CompanyName":"Hertz","Cars":"Ford, BMW, Fiat"},
"02":{"CompanyName":"Avis","Cars":"Dodge, Nash, Buick"}}
How can I use the Formatting parameter to make the result look like this:
{"01":{"CompanyName":"Hertz","Cars":["Ford", "BMW", "Fiat"]},
"02":{"CompanyName":"Avis","Cars":["Dodge", "Nash", "Buick"]}}
As #dbc mentioned in the comments, you cannot use the Formatting parameter of JsonConvert.SerializeObject to affect whether a particular value in the JSON is surrounded by square brackets or not. The Formatting parameter only controls whether or not Json.Net will add indenting and line breaks to the JSON output to make it easier to read by a human.
In JSON, square brackets are used to denote an array of values, as opposed to a single value. So, if you want to add square brackets for a particular property, the easiest way to do that is to change how that property is declared in your class such that it correspondingly represents an array (or list).
Based on your original JSON, I'm assuming you have a class which looks like this:
public class Company
{
public string CompanyName { get; set; }
public string Cars { get; set; }
}
...and you are creating your JSON something like this:
var results = new Dictionary<string, Company>();
results.Add("01", new Company
{
CompanyName = "Hertz",
Cars = "Ford, BMW, Fiat"
});
results.Add("02", new Company
{
CompanyName = "Avis",
Cars = "Dodge, Nash, Buick"
});
string json = JsonConvert.SerializeObject(results);
To get square brackets in the JSON, change the type of your Cars property from string to List<string>:
public class Company
{
public string CompanyName { get; set; }
public List<string> Cars { get; set; }
}
Of course, you will also need to make a corresponding change to the code which populates the results:
var results = new Dictionary<string, Company>();
results.Add("01", new Company
{
CompanyName = "Hertz",
Cars = new List<string> { "Ford", "BMW", "Fiat" }
});
results.Add("02", new Company
{
CompanyName = "Avis",
Cars = new List<string> { "Dodge", "Nash", "Buick" }
});
string json = JsonConvert.SerializeObject(results);
Here is a short demo: https://dotnetfiddle.net/lgg9jk

Correctly convert DateTime property with Dapper on SQLite

I'm using Dapper to insert and get objects to/from SQLite: one object have a property of type DateTime (and DateTimeOffset) that I have to persist and retrieve with milliseconds precision. I can't find a way to correctly retrieve the value because Dapper fail with:
System.FormatException : String was not recognized as a valid DateTime.
in System.DateTimeParse.ParseExactMultiple(String s, String[] formats, DateTimeFormatInfo dtfi, DateTimeStyles style)
in System.DateTime.ParseExact(String s, String[] formats, IFormatProvider provider, DateTimeStyles style)
in System.Data.SQLite.SQLiteConvert.ToDateTime(String dateText, SQLiteDateFormats format, DateTimeKind kind, String formatString)
in System.Data.SQLite.SQLite3.GetDateTime(SQLiteStatement stmt, Int32 index)
in System.Data.SQLite.SQLite3.GetValue(SQLiteStatement stmt, SQLiteConnectionFlags flags, Int32 index, SQLiteType typ)
in System.Data.SQLite.SQLiteDataReader.GetValue(Int32 i)
in System.Data.SQLite.SQLiteDataReader.GetValues(Object[] values)
in Dapper.SqlMapper.<>c__DisplayClass5d.<GetDapperRowDeserializer>b__5c(IDataReader r) in SqlMapper.cs: line 2587
in Dapper.SqlMapper.<QueryImpl>d__11`1.MoveNext() in SqlMapper.cs: line 1572
in System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
in System.Linq.Enumerable.ToList(IEnumerable`1 source)
in Dapper.SqlMapper.Query(IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Boolean buffered, Nullable`1 commandTimeout, Nullable`1 commandType) in SqlMapper.cs: line 1443
in Dapper.SqlMapper.Query(IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Boolean buffered, Nullable`1 commandTimeout, Nullable`1 commandType) in SqlMapper.cs: line 1382
What do I have to try? Column is of type DATETIME.
Do I have to create a custom TypeHandler and convert DateTime to and from a SQLite string in format "o"?
Dapper version 1.38
I know it's old, but I have found the solution.
After a lot of digging and analyzing Dapper code I came up with this (notice that this is 2019 year):
First you will have to create date time handler:
public class DateTimeHandler : SqlMapper.TypeHandler<DateTimeOffset>
{
private readonly TimeZoneInfo databaseTimeZone = TimeZoneInfo.Local;
public static readonly DateTimeHandler Default = new DateTimeHandler();
public DateTimeHandler()
{
}
public override DateTimeOffset Parse(object value)
{
DateTime storedDateTime;
if (value == null)
storedDateTime = DateTime.MinValue;
else
storedDateTime = (DateTime)value;
if (storedDateTime.ToUniversalTime() <= DateTimeOffset.MinValue.UtcDateTime)
return DateTimeOffset.MinValue;
else
return new DateTimeOffset(storedDateTime, databaseTimeZone.BaseUtcOffset);
}
public override void SetValue(IDbDataParameter parameter, DateTimeOffset value)
{
DateTime paramVal = value.ToOffset(this.databaseTimeZone.BaseUtcOffset).DateTime;
parameter.Value = paramVal;
}
}
Now, notice that Dapper translates .Net's type DateTimeOffset to dbType - DateTimeOffset. You need to remove this mapping and add your own like this:
SqlMapper.RemoveTypeMap(typeof(DateTimeOffset));
SqlMapper.AddTypeHandler(DateTimeHandler.Default);
That's all. Now everytime Dapper will see DateTimeOffset property in your model, it will run your DateTimeHandler to manage this.
I have found that custom TypeHandler for base types can't be used because of default typeMap that is choosen before looking for TypeHandler.
I have opened an issue dapper-dot-net but in the mean time I have solved replacing via reflection the default typeMap with a new one like the previous minus the four key DateTime, DateTime?, DateTimeOffset, DateTimeOffset?
I've made a slight modification to Adam Jachocki's solution as it didn't work for me. I am storing a date as TEXT in Sqlite and Dapper was giving me a string instead of a DateTime as the object value to parse. Apparently, Sqlite stores datetime values using three different data types: INTEGER (unix epoch), TEXT (ISO 8601 YYYY-MM-DD HH:MM:SS.SSS), and REAL ("number of days since noon in Greenwich on November 24, 4741 B.C."). That last one is really out there, so it isn't supported in the code below.
See the sqlite docs and this page for more info.
Below is my implementation of the DateTimeOffset TypeHandler. The rest of Adam's solution remains the same.
internal class DateTimeOffsetHandler : SqlMapper.TypeHandler<DateTimeOffset>
{
private static readonly TimeZoneInfo databaseTimeZone = TimeZoneInfo.Local;
private static readonly DateTime unixOrigin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
public static DateTimeOffsetHandler Default { get; } = new DateTimeOffsetHandler();
public DateTimeOffsetHandler() {}
public override DateTimeOffset Parse(object value)
{
if (!TryGetDateTime(value, out DateTime storedDateValue))
{
throw new InvalidOperationException($"Unable to parse value {value} as DateTimeOffset");
}
if (storedDateValue.ToUniversalTime() <= DateTimeOffset.MinValue.UtcDateTime)
{
return DateTimeOffset.MinValue;
}
else
{
return new DateTimeOffset(storedDateValue, databaseTimeZone.BaseUtcOffset);
}
}
public override void SetValue(IDbDataParameter parameter, DateTimeOffset value)
{
DateTime paramVal = value.ToOffset(databaseTimeZone.BaseUtcOffset).DateTime;
parameter.Value = paramVal;
}
private bool TryGetDateTime(object value, out DateTime dateTimeValue)
{
dateTimeValue = default;
if (value is DateTime d)
{
dateTimeValue = d;
return true;
}
if (value is string v)
{
dateTimeValue = DateTime.Parse(v);
return true;
}
if (long.TryParse(value?.ToString() ?? string.Empty, out long l))
{
dateTimeValue = unixOrigin.AddSeconds(l);
return true;
}
if (float.TryParse(value?.ToString() ?? string.Empty, out float f))
{
throw new InvalidOperationException("Unsupported Sqlite datetime type, REAL.");
}
return false;
}
}

RavenDB query by index and DateTime

I'm quite new to RavenDB so sorry if my question sounds stupid. I have a class which contains a DateTime property. I store instances of this class in RavenDB. I have defined index the following way:
from doc in docs.Orders
from docItemsItem in ((IEnumerable<dynamic>)doc.Items).DefaultIfEmpty()
select new { Items_Price_Amount = docItemsItem.Price.Amount, Items_Quantity = docItemsItem.Quantity, Date = doc.Date }
http://dl.dropbox.com/u/3055964/Capture.GIF <-- here's a screenshot
Here's class definition:
public class Order
{
public DateTime Date { get; set; }
public IList<OrderItem> Items { get; set; }
public string CustomerId { get; set; }
public Order()
{
Items = new List<OrderItem>();
}
}
Now, when I try to query RavenDB with the index shown above, the query yields no result at all.
var orders = session.Query<Order>("OrdersIndex").Where(o => o.Date > DateTime.Now).ToList(); // orders.Count == 0
If I omit the index from query, like this:
var orders = session.Query<Order>().Where(o => o.Date > DateTime.Now).ToList(); // orders.Count == 128
a temporary index is created and eveything works as expected.
Does anyone has any idea what's wrong with my query?
Thanks.
UPDATE
Allright, I removed Fields Date, Items,Price,Amount and Items,Quantity via management studio (shown in screenshot), and now the query works fine. Anyone any idea why? What's the purpose to define those fields explicitly?
Check that the date in the Index is stored as Index(x => x.Date, FieldIndexing.Default).
I had it set to FieldIndexing.Analysed, and wasn't getting the correct results back.
I need to read up more on the difference between the different FieldIndexing options :)

How to work with several fields in DateTime?

public DateTime EnterDeparture()
{
Console.WriteLine("Enter Year:");
return new DateTime().AddYears(int.Parse(Console.ReadLine()));
}
// This will return new DateTime(Without assigned Year) Cause DateTime is value type.
public DateTime EnterDeparture()
{
DateTime EnterDeparture = new DateTime();
Console.WriteLine("Enter Year:");
EnterDeparture.AddYears(int.Parse(Console.ReadLine()));
return EnterDeparture;
}
How to work with several fields in DateTime ? (Year,Days for example) Default constructors aren't suitable.
The DateTime.AddXXX methods return new DateTime instances, the existing struct does not change. Since each method returns a new instance, you can chain the method calls together. At the very least, you want to capture each return value into a variable. For example:
DateTime myDate = DateTime.Today;
DateTime tomorrowAtNoon = myDate.AddDays(1).AddHours(12);
You could have also written it like
DateTime tomorrow = myDate.AddDays(1);
DateTime tomorrowAtNoon = tomorrow.AddHours(12);
Follow?

Resources