I don't have too much experience with Sqlite in .Net, but the behaviour I see is rather strange. Let's say we have a .Net core app with following project.json
:
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.Data.Sqlite": "1.0.0",
"Dapper": "1.50.2"
},
"frameworks": {
"netcoreapp1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0"
}
},
"imports": "dnxcore50"
}
}
}
Also we have a simple class Item
:
public class Item
{
public Item() { }
public Item(int id, string name, decimal price)
{
this.Id = id;
this.Name = name;
this.Price = price;
}
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
Then I create an in-memory database and populate it with data (using Dapper):
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
connection.Execute("CREATE TABLE IF NOT EXISTS Items(Id INT, Name NVARCHAR(50), Price DECIMAL)");
var items = new List<Item>
{
new Item(1, "Apple", 3m),
new Item(2, "Banana", 1.4m)
};
connection.Execute("INSERT INTO Items(Id, Name, Price) VALUES (@Id, @Name, @Price)", items);
Then I try to read from the Items
table:
var dbItems = connection.Query<Item>("SELECT Id, Name, Price FROM Items").ToList();
When I run the solution, I get the following exception:
Unhandled Exception: System.InvalidOperationException: Error parsing column 2 (Price=1.4 - Double) ---> System.Invali dCastException: Unable to cast object of type 'System.Double' to type 'System.Int64'.
Ok, then I tried to use Microsoft.Data.Sqlite
to get the data:
var command = connection.CreateCommand();
command.CommandText = "SELECT Price FROM Items";
var reader = command.ExecuteReader();
while (reader.Read())
{
Console.WriteLine(reader[0].GetType());
}
As a result I get:
System.Int64 // Price = 3
System.Double // Price = 1.4
I tried running query on real database with decimal price, the data type returned is correct and is always decimal (as expected).
What direction should I dig further? Is something wrong with my in-memory database? How to make it consistent with decimals?
A column that uses INTEGER affinity behaves the same as a column with NUMERIC affinity. The difference between INTEGER and NUMERIC affinity is only evident in a CAST expression: The expression "CAST(4.0 AS INT)" returns an integer 4, whereas "CAST(4.0 AS NUMERIC)" leaves the value as a floating-point 4.0.
All "values" in SQLite3 (that is, at the intersection of any row and column) may contain data of any of the following datatypes: INTEGER , a 64-bit signed integer. REAL , a 64-bit IEEE-754 floating point number. TEXT , a bag-o-bytes that conforms to C String semantics containing UTF-8 or UTF-16 encoded data.
SQLite only has four primitive data types: INTEGER, REAL, TEXT, and BLOB. APIs that return database values as an object will only ever return one of these four types.
INTEGER – It is an integer, stored in 1, 2, 3, 4, 6, or 8 bytes depending on the value. REAL – It is a floating point value, stored as an 8-byte floating number. TEXT – It is a string, stored using the database encoding (UTF). BLOB – It is a group of data, stored exactly as it was entered.
In SQLite, if you create a Table with a Numeric/Decimal Column datatype, it's associated to Numeric type affinity and its default behavior is to store an integer if a value has no decimals, or to store a real number if value contains decimals. This helps to save bytes storage because the most used integer values (less than a million value for small to medium applications) needs less bytes than real data type:
Integer (approx):
Against 8 byte real IEEE datatype
If you need to store a real number always, it's necessary to declare a float/real datatype in order to be considered as real type affinity, thus even if you send an integer number to SQLite it would be stored as real
Source: https://sqlite.org/datatype3.html
I know real could be imprecise, but I've made some test using C# Decimal datatype and SQLite with database in disk in wal mode, and I've never had a rounding error if all operations are made in C#. But when I made some calculations directly in SQLite I got some rounding errors and some bad calculations because of 2 integer division
I use EF6 and Dapper to read/write Numeric values (integer/real from SQLite) and never got an error.
Now, I'm planning to take advantage of SQLite numeric behavior to save disk space with dapper TypeHandler, thus I may replicate SQL Server Money
implementation (internally, SQL Server saves an 8 byte integer and it's divided by 10000 when it loads into memory):
public class NumericTypeHandler : SqlMapper.TypeHandler<decimal>
{
public override void SetValue(IDbDataParameter parameter, decimal value)
{
parameter.Value = (long)(value / 10000);
}
public override decimal Parse(object value)
{
return ((decimal)value)/10000M;
}
}
Additionally, I set up datetime UnixEpoch implementation to save diskspace and enhance performance
Note:
SQLite may handle dozens of users in a low demand asp.net application, the trick is tune-up SQLite with pragmas directives and other things:
Also I made some enhancements in C# side:
avoid save unnecessary data (Error logs, email html text)
I think you should use an ORM like EFx or Dapper if you plan to use SQLite as backend. With dapper you would need to create your own micro framework to save development time (basic CRUD functionality and Linq queries transformation).
The best tool that I´ve found is https://github.com/linq2db/linq2db, you use linq for accessing database with very good speed and easiness of linq. And it is possible to make CRUD functionality.
I'll be happy if this answer helps
regards
This is a feature of SQLite, please see this link: https://sqlite.org/faq.html#q3
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With