SQLite limiting and ordering to page on iPhone - c#

I have an iPhone app, built in Xamarin, that stores objects to a local SQLite database. As it takes a long time to load all of the data, I am loading it 20 records at a time, and loading more as the user scrolls.
I am paging by CreatedDate, and this is stored as an Integer field by its DateTime.Ticks property (the number of 100 nanoseconds since DateTime.MinValue). I always pass the oldest loaded CreatedDate to the read function.
Integer column: 8 bytes signed
DateTime.Ticks: 8 bytes signed
The code to write the CreatedDate is as follows:
var param = cmd.CreateParameter();
param.ParameterName = "#date";
param.Value = myObject.DateCreated.Value.Ticks;
cmd.Parameters.Add(param);
And the code to read the next page is as follows:
cmd.CommandText = "SELECT * FROM MyTable WHERE CreatedDate < " +
(offset.HasValue ? offset.Value.Ticks : DateTime.MaxValue.Ticks) +
" ORDER BY CreatedDate DESC LIMIT " + pageSize;
I can tell that the dates are correct, because my sections in my UITableView are based on their CreatedDates.
So here's all the weird behavior (i.e. my question).
I started off in the simulator. At first, I was using DESC and it was returning the oldest objects. Then I switched it to ASC and it was returning the newest objects. This seems backwards. Then, it magically started working correctly with DESC. Paging began working in the simulator, and still works.
Then I switched to a real iPhone. The DESC and ASC issue showed up again, but this time has not fixed itself (ASC returns the most recent objects). Either way, when I scroll to the bottom of the UITableView, the load gets called again with the correct offset, but it gets no data back (and therefore all paging stops).
Things I know.
All of the data is in the table, because if I SELECT * I get everything with the correct dates back. I have also written all of the dates to the console to be sure.
My newest date is 635194634041230000
My oldest date is 635166796800000000
The offset date that returns no rows is 635193909600000000
Note. I'm OK with constructive criticism on how I'm doing this. I'll upvote if I like it enough. But if you post an answer with that information in it, please also try to address the current issue I'm having.

I'm not in front of a computer, so I can't really debug your code atm, however, I thought I'd link to some generic code I wrote for MonoTouch to dynamically page data from SQLite:
https://github.com/jstedfast/MonoTouch.SQLite/blob/master/MonoTouch.SQLite/SQLiteTableModel.cs
This code uses SQLite-net and not System.Data, but it may still help.
The core logic for paging in and out is in GetItem().

When you created your Database, what datatype did you set for the CreateData column? If you used Integer then Ticks should be ok.
1.2 Date and Time Datatype
SQLite does not have a storage class set aside for storing dates
and/or times. Instead, the built-in Date And Time Functions of SQLite
are capable of storing dates and times as TEXT, REAL, or INTEGER
values:
TEXT as ISO8601 strings ("YYYY-MM-DD HH:MM:SS.SSS"). REAL as Julian
day numbers, the number of days since noon in Greenwich on November
24, 4714 B.C. according to the proleptic Gregorian calendar. INTEGER
as Unix Time, the number of seconds since 1970-01-01 00:00:00 UTC.

Sadly, I did not post the relevant code. If the record didn't exist yet, I was writing DateTime.Now to the column. So depending on what order the WCF service returned the results (descending or ascending by date), my ordering could be backwards. I'm not quite sure why it was refusing to page, but I'm guessing it had something to do with precision or whether the database was persisting on the device. Both problems are fixed.

Related

Postgres and c# - DateTime with timezone confusion

Hopefully not a repost of another... a rather simple question, but I think I'm fundamentally confused about how dates/times with timezones are handled between Postgres and c#.
Simple situation, at one point in the program I call DateTime.Now and save to a variable 'now'. This is inserted into a Postgres DB.
cmd.Parameters.AddWithValue("createdat", now);
Great. The column that this is inserted into is of type "timestamp with time zone". When I view the result from something like DataGrip or DBeaver the raw data is exactly what I want (I think).
2021-03-09 15:07:51
Later on I query this table in a rather simple way.
SELECT createdat FROM mytable WHERE mycondition;
I cast it as a c# DateTime type and populate a class with it.
while (reader.Read())
{
myClass.createdAt = (DateTime) reader["createdat"];
}
However, the value is populated as the following:
3/9/2021 10:07:51 AM
Something is not working here. First, if the column in the DB is of timestamp with time zone, where is the time zone information? Second, if the time zone is working, since it is being both populated and queried from the same computer, why is there a discrepancy? Third, and the pressing issue, how do I solve this? Must I explicitly define the time zone? I'm confused as to why it is returning different data... Hopefully, someone can help me out.
I think Dbeaver is changing the time zone, but the data inserted is the same you get on c#. Try to check directly on the server using commandline because maybe dbeaver is changing it to diplay on your pc

Approach to storing and retrieving time when having multiple systems talking to each other

Consider this scenario with two applications whose data is synced
Application 1 | Application 2
Data from application1 is inserted/updated into application2 based on last modified date of records.
How can we make sure that the last modified date is not dependent on time zones.
I would use a rowversion column in both applications. To decide what to sync you can have a query or a merge statement and in the where clause you can compare the rows table1.RowVersion > table2.RowVersion. (table1 is from application1 and table2 is from application2)
This might help: ToUniversalTime
Convert the times to universal times, these take into account the time zone, daylight savings, etc. If you convert both times before adding the records, you should be able to get the correct last modified date.

MS SQL how to record negative time

I have a column in the MS SQL database with type “TIME”. I need to record a negative value in it. It isn't a range between two certain times or dates. It’s an abstract value of time that is used in calculations. In the entity framework it is treated as a TimeSpan, which MetaData.tt does automatically with all TIME defined columns in the database.
For example, I might have an arbitrary calendar with events at 5AM and 8PM on Monday, one at 4PM on Tuesday, and one at 3AM on the Sunday after that. I could add the value to these times and get a time either before (in case of negative time), or after (in case of positive time) the beginning of the event.
When I try to write down a negative value, the database refuses it.
The recording of entity to database goes by direct bind of post attributes in the controller, so if it needs to be translated into ticks, is it reliable to do so in Javascript? How would I go about the input textbox for it? It looks like I cannot separate the value from the content in a text box. And if I have to turn it into an INT, I cannot use #EditorFor on it anymore, creating another point of fault where code becomes less flexible.
It almost feels like I should create new columns to denote the negativity of these values and use a dropdown list with hidden inputs instead of a textbox.
EDIT:
Why avoid non-time types:
Consider this code:
var typeName = _code.Escape(edmType.MetadataProperties.Where(…).FirstOrDefault());
If the EDM property has the type int, the generated code will be the type int.
The EDM property comes from the database itself, so if it is not a type that translates directly into a time, then there will need to be a new method (somewhere in a helper, perhaps), which translates this into a time. This new method will have to be maintained (by other people on the team), which means a weak point, because if someone changes the column name, now the code will not just get properly generated again.
Errors may also not be available through the error log, since most properties also tend to be referenced in javascript at some point (which is often also generated, and now can't be for this column because it is a special case). I'm also talking about some 20 columns suffering from this, so this has a very good potential to quickly turn into a deeply tangled ball of spaghetti.
It really seems like you are trying to store a duration, not a time. TIME is used for storing a point in time, not an amount of time. I would choose some subdivision of time (second, millisecond, etc), and store that as an int (or bigint if necessary). Then in SQL Server you could use DATEADD(SECOND,#storedValue,#dateToChange) to calculate the true time or DateTime.Add.Milliseconds(storedValue) or DateTime.Add.Seconds(storedValue), so on, in C# when trying to calculate the time you want.
Let me know if I'm missing something.
In these cases I think I would store both Begin and End times and have another Computed Column to store the difference (with INT datatype) using DATEDIFF:
CREATE TABLE dbo.MyTable
(
Time1 time
Time2 time
timedifference AS DATEDIFF(Second, Time1,Time2)
);
Then you can convert the Seconds into a time of day like this:
SELECT CONVERT(varchar, DATEADD(ms, timedifference * 1000, 0), 114)
Here is a working sample of what you will get:
SELECT CONVERT(varchar, DATEADD(ms, DATEDIFF(Second, CAST('12:24:18.3500000' as time),CAST('11:24:18.3500000' as time)) * 1000, 0), 114)
SELECT CONVERT(varchar, DATEADD(ms, DATEDIFF(Second, CAST('11:24:18.3500000' as time),CAST('12:24:18.3500000' as time)) * 1000, 0), 114)
Database type time does not support negative values. The acceptable range is 00:00:00.0000000 through 23:59:59.9999999
https://msdn.microsoft.com/en-us/library/bb677243.aspx

.NET DataRowComparer DateTime Comparison To T-SQL DateTime

I have learned that SQL Server stores DateTime differently than the .NET Framework. This is very unfortunate in the following circumstance: Suppose I have a DataRow filled from my object properties - some of which are DateTime - and a second DataRow filled from data for that object as persisted in SQL Server:
DataRow drFromObj = new DataRow(itemArrayOfObjProps);
DataRow drFromSQL = // blah select from SQL Server
Using the DataRowComparer on these two DataRows will give an unexpected result:
// This gives false about 100% of the time because SQL Server truncated the more precise
// DateTime to the less precise SQL Server DateTime
DataRowComparer.Default.Equals(drFromObj, drFromSQL);
My question was going to be, 'How do other people deal with reality in a safe and sane manner?' I was also going to rule out converting to strings or writing my own DataRowComparer. I was going to offer that, in absence of better advice, I would change the 'set' on all of my DateTime properties to convert to a System.Data.SqlTypes.SqlDateTime and back upon storage thusly:
public Nullable<DateTime> InsertDate
{
get
{
if (_InsDate.HasValue)
return _InsDate;
else
return null;
}
set
{
if (!object.ReferenceEquals(null, value) && value.HasValue)
_InsDate = (DateTime)(new System.Data.SqlTypes.SqlDateTime(value));
}
}
I know full well that this would probably get screwed up as I used the _InsDate variable directly somewhere rather than going through the property. So my other suggestion was going to be simply using System.Data.SqlTypes.SqlDateTime for all properties where I might want a DateTime type to round trip to SQL Server (and, happily, SqlDateTime is nullable). This post changed my mind, however, and seemed to fix my immediate problem. My new question is, 'What are the caveats or real world experiences using the SQL Server datetime2(7) data type rather than the good, old datetime data type?'
TL;DR: Comparing dates is actually hard, even though it looks easy because you get away with it most of the time.
You have to be aware of this issue and round both values yourself to the desired precision.
This is essentially the same issue as comparing floating point numbers. If two times differ by four nanoseconds, does it make sense for your application to consider them different, or the same?
For example, if two servers have logged the same event, searching for corresponding records, you wouldn't say "no that can't be the correct event because the time is wrong by 200 nanoseconds". Clocks can differ by that amount on two servers no matter how hard they try to keep their time synchronised. You might accept that an event seen on server A and logged with a time a couple of seconds after the time on server B might have been actually seen simultaneously or the other way around.
Note:
If you are comparing data which is supposed to have made some sort of round-trip out of the database, you may find it has been truncated to the second or minute. (For example if it has been through Excel or an old VB application, or been written to a file and parsed back in.)
Data originating from external sources is generally rounded to the day, the minute, or the second. (except sometimes logfiles, eventlogs or electronic dataloggers, which may have milliseconds or better)
If the data has come from SQL Server, and you are comparing it back to itself (for example to detect changes), you may not encounter any issues as they will be implicitly truncated to the same precision.
Daylight savings and timezones introduce additional problems.
If searching for dates, use a date range. And make sure you write the query in such a way that any index can be used.
Somewhat related:
Why doesn't this sql query return any results comparing floating point numbers?
Identity increments. Sort by Identity and you get insert order. You (can) control insert order.
I seriously doubt output would ever by out of order but if you don't trust it you can use #SeeMeSorted
DECLARE #SeeMeSort TABLE
( [ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](20) NOT NULL);
DECLARE #SeeMeSorted TABLE
( [ID] [int] primary key NOT NULL,
[Name] [nvarchar](20) NOT NULL);
insert into #SeeMeSort ([Name])
OUTPUT INSERTED.[ID], INSERTED.[name]
values ('fff'), ('hhh'), ('ggg');
insert into #SeeMeSort ([Name])
OUTPUT INSERTED.[ID], INSERTED.[name]
into #SeeMeSorted
values ('xxx'), ('aaa'), ('ddd');
select * from #SeeMeSorted order by [ID];

What is the best approach to calculate a formula which changes value each day?

I use the following columns stored in a SQL table called tb_player:
Date of Birth (Date), Times Played (Integer), Versions (Integer)
to calculate a "playvalue" (integer) in the following formula:
playvalue = (Today - Date of Birth) * Times Played * Versions
I display upto 100 of these records with the associataed playvalue on a webpage at any time.
My question is, what is the most efficient way of calculating this playvalue given it will change only once a day, due to the (today-date of birth) changing? The other values (times played & versions) remain the same.
Is there a better way than calculating this on the fly each time for the 100 records? If so, is it more efficient to do the calculation in a stored proc or in VB.NET/C#?
In a property/method on the object, in C#/VB.NET (your .NET code).
The time to execute a simple property like this is nothing compared to the time to call out-of-process to a database (to fetch the rows in the first place), or the transport time of a web-page; you'll never notice it if just using it for UI display. Plus it is on your easily-scaled-out hardware (the app server), and doesn't involve a huge update daily, and is only executed for rows that are actually displayed, and only if you actually query this property/method.
Are you finding that this is actually causing a performance problem? I don't imagine it would be very bad, since the calculation is pretty straightforward math.
However, if you are actually concerned about it, my approach would be to basically set up a "playvalue cache" column in the tb_player table. This column will store the calculated "playvalue" for each player for the current day. Set up a cronjob or scheduled task to run at midnight every day and update this column with the new day's value.
Then everything else can simply select this column instead of doing the calculation, and you only have to do the calculation once a day.

Categories

Resources