4

I am using Sqlite.Net in my Xamarin.Forms application. So far it has been great at returning lists of objects if my object is a class like so:

SqliteDatabase.Connection.Query<Customer>("Select * from Customers");

I would now like to return the equivalent of a DataSet dynamically from my query

SqliteDatabase.Connection.Query("Select * from Customers inner join Calls on Customers.Id=Calls.CustomerId")

Now from the second query I would like to return a DataSet instead of a list of objects. I know I could create a new object which combines the columns of Customers and Calls but I don't want to have to create objects every time I want to query the database.

Is it possible to just dynamically return a Dataset or Object?

Community
  • 1
  • 1
JKennedy
  • 18,150
  • 17
  • 114
  • 198
  • what problems are you experiencing in regards to returning a `DataSet or a DataTable` this is not very difficult.. hint do a google search on the `.Fill()` method. there are lots of examples on how to do this currently on stackoverflow.. – MethodMan Aug 23 '16 at 16:27
  • @MethodMan are you sure we are talking about the same Sqlite wrapper? The `Fill` method does not exist on the `Sqlite.Net-PCL` I am using – JKennedy Aug 23 '16 at 16:32

5 Answers5

4

In the end I actually managed to come up with a method that will run any query and return the rows as items in the list and the columns as objects in the array:

    public List<object[]> RunSql(string sqlString, bool includeColumnNamesAsFirstRow)
    {
        var lstRes = new List<object[]>();
        SQLitePCL.sqlite3_stmt stQuery = null;
        try
        {
            stQuery = SQLite3.Prepare2(fieldStrikeDatabase.Connection.Handle, sqlString);
            var colLenght = SQLite3.ColumnCount(stQuery);

            if (includeColumnNamesAsFirstRow)
            {
                var obj = new object[colLenght];
                lstRes.Add(obj);
                for (int i = 0; i < colLenght; i++)
                {
                    obj[i] = SQLite3.ColumnName(stQuery, i);
                }
            }

            while (SQLite3.Step(stQuery) == SQLite3.Result.Row)
            {
                var obj = new object[colLenght];
                lstRes.Add(obj);
                for (int i = 0; i < colLenght; i++)
                {
                     var columnType = SQLitePCL.raw.sqlite3_column_decltype(stQuery, i);

                     switch (columnType)
                     {
                         case "text":
                              obj[i] = SQLite3.ColumnString(stQuery, i);
                              break;
                         case "int":
                               obj[i] = SQLite3.ColumnInt(stQuery, i);
                               break;
                         case "real":
                               obj[i] = SQLite3.ColumnDouble(stQuery, i);
                               break;
                         case "blob":
                               obj[i] = SQLite3.ColumnBlob(stQuery, i);
                               break;
                         case "null":
                               obj[i] = null;
                               break;
                      }
                }
            }
            return lstRes;
        }
        catch (Exception)
        {
            return null;
        }
        finally
        {
            if (stQuery != null)
            {
                SQLite3.Finalize(stQuery); 
            }
        }
    }
JKennedy
  • 18,150
  • 17
  • 114
  • 198
  • What is the value of `startColumnIndex`? It seem its not defined in your codes. – Mr Hery May 15 '20 at 01:40
  • That was actually a bug in my code, I've removed that property. In my production App I was using `StartColumnIndex` to offset my column count for a different reason – JKennedy May 15 '20 at 07:50
  • Well done. This was exactly what I was also looking for. Coming from work with older codebases that are all done via SQL calls, sql-net-pcl just wasn't very useful without the ability to easily display the results of arbitrary queries. – WiredEarp May 17 '21 at 10:01
1

SQLite.NET PCL is a .NET wrapper around sqlite.

Therefore you can query similar to EF by using a join in in LINQ or Lambda than in the Query. The wrapper will handle the conversion to sqlite query for you.

You can then return a new datatype of the joined type or a dynamic type.

Note : Joins are not directly supported in sqlite (more info) and work around is mentioned here.

Sample code:

var conn = new SQLiteConnection(sqlitePlatform, "foofoo");
var query = from customer in conn.Table<Customers>().ToList()
            join call in conn.Table<Calls>().ToList()
                         on customer.ID equals call.CustomerId                
            select new { Customer = customer , Calls = call };

Lambda version:

conn.Table<Customer>().ToList().Join
(conn.Table<Call>().ToList(),
customer => customer.Id,
call => call.CustomerId, 
(customer, call) => new { Customer = customer, Calls = call });
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Rohit Vipin Mathews
  • 11,629
  • 15
  • 57
  • 112
  • 1
    Do you have an example of how this is done so I can try it? – JKennedy Aug 24 '16 at 07:36
  • 1
    Thanks for the example but unfortunately it doesn't work. You get a `System.NotSupportedException: Joins are not supported.` Is there an alternative library or code I can try? – JKennedy Aug 24 '16 at 08:15
  • 1
    This didn't work in `Xamarin.iOS` because ios does not support `Dynamic` objects – JKennedy Jan 15 '20 at 15:05
  • This is however over, but for anyone seeing it. this Lamda approach is slower as it fetch all data to memory to List before join happen. With 1000s of rows it become pain fully slow. – Sumit Gupta Feb 08 '23 at 06:04
0

thank u so much user1! works perfect. here is just an example how to use ur method:

var objects = mySQLiteConnection.RunSql("SELECT * FROM Persons", true);

// ColumnNames
List<string> ColumnNames = new List<string>();
for (int column = 0; column < objects[0].Length; column++)
{
    if (objects[0][column] != null) spaltennamen.Add((string)objects[0][column]);
}

// RowValues
for (int row = 1; row < objects.Count; row++)
{
    for (int column = 0; column < objects[row].Length; column++)
    {
        if (objects[row][column] != null) System.Diagnostics.Debug.WriteLine(spaltennamen[column] + " : " + objects[row][column]);
    }
}
0

It sounds like what you want to do is essentially recreate ADO.NET. When you say "DataSet", I'm guessing that you are talking about ADO.NET. This probably means that you don't want to use the ORM functionality built in to the SQLite.Net library.

I have created this version of the library that will allow you to do flat table reads from an SQLite database. It means that you CAN read the data in to an ADO.NET dataset if you like.

https://github.com/MelbourneDeveloper/SQLite.Net.Standard

Christian Findlay
  • 6,770
  • 5
  • 51
  • 103
0

Unlike @Fabian Monkemoller, i was unable to get @User1's code to work straight away. This is a modified version that make use of nullable reference types and method-nesting to seperate the main-code from the try-catch block:

     public static object?[][]? ToDataSet(this SQLiteConnection sqlConnection, string query , bool includeColumnNamesAsFirstRow = true)
    {
        var stQuery = SQLite3.Prepare2(sqlConnection.Handle, query );
        var colLength = SQLite3.ColumnCount(stQuery);
        try
        {
            return SelectRows().ToArray();
        }
        catch (Exception e)
        {
            return null;
        }
        finally
        {
            if (stQuery != null)
            {
                SQLite3.Finalize(stQuery);
            }
        }

        IEnumerable<object?[]> SelectRows()
        {
            if (includeColumnNamesAsFirstRow)
            {
                yield return SelectColumnNames(stQuery, colLength).ToArray();
            }

            while (SQLite3.Step(stQuery) == SQLite3.Result.Row)
            {
                yield return SelectColumns(stQuery, colLength).ToArray();
            }

            static IEnumerable<object> SelectColumnNames(SQLitePCL.sqlite3_stmt stQuery, int colLength)
            {
                for (int i = 0; i < colLength; i++)
                {
                    yield return SQLite3.ColumnName(stQuery, i);
                }
            }

            static IEnumerable<object?> SelectColumns(SQLitePCL.sqlite3_stmt stQuery, int colLength)
            {
                for (int i = 0; i < colLength; i++)
                {
                    var x = SQLitePCL.raw.sqlite3_column_decltype(stQuery, i);
                    yield return x switch
                    {
                        "text" => SQLite3.ColumnString(stQuery, i),
                        "integer" => SQLite3.ColumnInt(stQuery, i),
                        "bigint" => SQLite3.ColumnInt64(stQuery, i),
                        "real" => SQLite3.ColumnDouble(stQuery, i),
                        "blob" => SQLite3.ColumnBlob(stQuery, i),
                        "null" => null,
                        _ => throw new Exception($"Unexpected type encountered in for query {stQuery}")
                    };
                }
            }
        }
    }
Declan Taylor
  • 408
  • 6
  • 8