0

I'm fairly new to SQL and SQLite. What I'm trying to do is: I have three different classes, which are related. Obj1, Obj2 and Obj3. The relation is that Obj1 one has one or more Obj2 and Obj2 has one or more Obj3. The way I organized my models to save in the database is the following:

class Obj1
{
 int Id
 string Name
}

class Obj2
{
 int Id
 int Obj1Id
 string Name
}
class Obj3
{
 int Id
 int Obj2Id
 string Name
}

In my UI, the user can create the instances as they want and the classes are organized as the following in my ViewModel (for binding purposes):

class Obj1
{
 int Id
 string Name
 List<Obj2> objs2
}
class Obj2
{
 int Id
 List<Obj3> objs3
 string Name
}
class Obj3
{
 int Id
 string Name
}

By the time I want to save the objects in database, I don't have any ids to relate to. I don't want to save objects and then query the Id that SQLite automatically gave to it back. How could I do this in a single sql stament? Is that possible?

In another queries, I'm using something like:

public static async Task<bool> SaveClient(Client client)
     {
         using (IDbConnection cnn = new SQLiteConnection(LoadConnectionString()))
           {
              await cnn.ExecuteAsync("insert into Client (Name, CreationDate, ServerPath) values (@Name, @CreationDate, @ServerPath)", client);
            }
     }

ASh
  • 34,632
  • 9
  • 60
  • 82
  • normally a DB-API offers some kind of LastID or InsertedID to return a generated primary key (without requery) – draz Mar 02 '22 at 12:17
  • You should use an ORM. If you don't wish to, You should send XML or JSON objects to the server, and then parse them on the SQL side. https://www.sqlshack.com/how-to-parse-json-in-sql-server/ – Morten Bork Mar 02 '22 at 12:27
  • I tried to use ```cnn.ExecuteAsync("select last_insert_rowid()")``` but it's giving me a wierd Id that does not match the last one inserted. Maybe I'm using it wrong, but.. I could use some help with that too, that would work for me – Leandro Manes Mar 02 '22 at 12:27
  • Last_insert_rowId is literally just that, meaning if ANYTHING on the server did something that resulted in a new ID, it would be from ANY source in the system, the code scope, isn't taken into account, that is likely why you are getting an odd id. Although by the definition I can find, it actually states that it is the last key Generated by the CONNECTION. This is interesting. – Morten Bork Mar 02 '22 at 12:28
  • @MortenBork I am trying to learn how to do it from scratch, to better understand ORMs. But im seriously thinking about use entity framework. – Leandro Manes Mar 02 '22 at 12:28
  • I applaud your dedication to the craft. It is a good idea, to understand what is ACTUALLY happening, instead of letting on ORM handle it for you. Always write your business logic, so you don't have a direct dependency on frameworks. (Doesn't mean do use them, just be able to avoid them, when they get annoying) – Morten Bork Mar 02 '22 at 12:30
  • @MortenBork is there a way to do it in a single sql statement? I could save a object, and then query it back to get the Id, but I'm concerned about performance. – Leandro Manes Mar 02 '22 at 12:32
  • @MortenBork Yeah, I agree with that. I use other frameworks in this application. I'm kind receptive with that. I was just curious. For now, I'm gonna apply ef and when I have some spare time I will go deeper in sql! Thank you for your answers – Leandro Manes Mar 02 '22 at 12:34
  • https://stackoverflow.com/questions/30715802/inserting-and-returning-autoidentity-in-sqlite3 Sorry, wrong link :D – Morten Bork Mar 02 '22 at 12:38
  • As an alternative to autoincrement you always have the option to use a pre generated id. A typical option is to use GUIDs for that that can simply be generated without using the target database and are unique by design. So without the hassle to get the generated id after an insert you can just create them beforehand. – Ralf Mar 02 '22 at 13:12
  • @MortenBork *You should use an ORM* - part way there; they're using Dapper – Caius Jard Mar 02 '22 at 14:26
  • Try doing it as one batch `insert ....; select last_insert_rowid();` also you need `ExecuteScalarAsync` not `ExecuteAsync` if you expect a single value result – Charlieface Mar 02 '22 at 15:10
  • @Charlieface thanks for your comment. I was just in this question to post the answer. something like you said! – Leandro Manes Mar 02 '22 at 16:45

2 Answers2

0

So. Firstly I want to thank all the commenters. They helped me get to the solution. What I did was:

int objId = await cnn.QuerySingleAsync<int>("insert into ObjTable (Name) values (@Name); select last_insert_rowid()", obj);

This way, I get back the Id from the object and then I can use on the subsequent logic.

I want to point it out that I also used a transaction to rollback if anything goes sideways.

  • `QuerySingleAsync` is meant for one whole row of data. As I said, for just one value (one column) you really want `ExecuteScalarAsync`. See https://www.learndapper.com/selecting-scalar-values – Charlieface Mar 02 '22 at 16:49
  • could you ellaborate a little more? I'm pretty new. I will edit the answer with your suggestion. – Leandro Manes Mar 02 '22 at 17:26
  • `QuerySingleAsync` is mean to retrieve a single row of data with multiple columns and map it into a single object (as opposed to a `List`). Whereas `ExecuteScalarAsync` is designed for just a single-row single-column dataset, and will retrieve just a single simple value. It seems `QuerySingleAsync` does work to do the same thing, but it's a bit of hack and probably not as efficient – Charlieface Mar 02 '22 at 23:39
0

Here is an abbreviated set of code that may help you along with SQLite. It is heavily commented and should be able to help without having to explicitly write your SQL inserts and parameterizing them. You can do by using an instance of a given class structure, and SQLite handles inserts and updates, provided there is an identified PRIMARY KEY / Auto-Increment. You could get into more reflection, generics and such, but this was just a simple piece-at-a-time approach.

using SQLite;
using System.Collections.Generic;

namespace MyApp.SQLiteData
{
    // This tells SQLite the name of the table you want when created
    [Table("MyTable1")]
    // This is the class structure that corresponds.  Can be the same name,
    // just wanted to show context of pieces as different
    public class ClassOfMyTable1
    {
        // This would be your auto-increment column in the table
        [PrimaryKey, AutoIncrement]
        public int MyTable1ID { get; set; }

        [MaxLength(4)]
        public string SomeString { get; set; } = "";  // so default is never null

        [MaxLength(50)]
        public string AnotherString { get; set; } = "";  // same, default empty string
    }

    public class SQLiteWrapper
    {
        // where do you want to store your database
        private static string _pathToMyDatabase = @"C:\Users\Public\TestFolder\YourDatabase.sqlite";

        public static void CreateDatabaseAndTables()
        {
            using (var db = new SQLiteConnection(_pathToMyDatabase))
            {
                db.CreateTable<ClassOfMyTable1>();
            }
        }

        public static int SaveToMyTable1( ClassOfMyTable1 yourRecord )
        {
            using (var db = new SQLiteConnection(_pathToMyDatabase))
            {
                var insUpdAnswer = 0;

                if (yourRecord.MyTable1ID == 0)
                    insUpdAnswer = db.Insert(yourRecord);
                else
                    insUpdAnswer = db.Update(yourRecord);

                // did something fail?  If anything, it would be a new auto-increment ID, OR a 1 indicating update completed
                if (insUpdAnswer == 0)
                    // record was NOT saved, some problem, return 0
                    return 0;

                // if it WAS saved, the auto-increment property will have been updated to the object passed in
                return yourRecord.MyTable1ID;
            }
        }

        public static void SampleToCreateEntry()
        {
            var myObj = new ClassOfMyTable1();
            myObj.SomeString = "Hello";
            myObj.AnotherString = "Testing";
            
            if( SaveToMyTable1( myObj ) > 0)
            {
                // Now, I have the ID that I can apply to your other entries too.
                // by Using the SQLite objects to handle the insert/update makes
                // things so much simpler.
                var yourOtherObject = new OtherClass();
                yourOtherObject.KeyIDFromFirstTable = myObj.MyTable1ID;

                // etc..., you can wrap save functions for your other tables too.
            }
        }

        public static List<ClassOfMyTable1> GetMyTableRecords()
        {
            using (var db = new SQLiteConnection(_pathToMyDatabase))
            {
                // just a SAMPLE to retrieve records with a parameterized
                // query.  The "?" is the place-holder for the parameter
                // value provided in the objects list following.
                var recs = db.Query<ClassOfMyTable1>(
@"select 
        * 
    from 
        MyTable1
    where 
        MyTable1ID > ?", new object[] { 5 });

                return recs;
            }
        }

        public static ClassOfMyTable1 GetOneMyTableRecord(int IdToGet)
        {
            using (var db = new SQLiteConnection(_pathToMyDatabase))
            {
                // just a SAMPLE to retrieve records with a parameterized
                // query.  The "?" is the place-holder for the parameter
                // value provided in the objects list following.
                var recs = db.Query<ClassOfMyTable1>(
@"select 
        * 
    from 
        MyTable1
    where 
        MyTable1ID = ?", new object[] { IdToGet });

                // if this query above, notice the parameter is for the 
                // ID you want to retrieve.  
                // If not found, return null... but you could also return 
                // a NEW instance of the object to its at least a valid object
                if (recs is null || recs.Count == 0)
                    return null;

                // if returned, just return the first (only) entry returned.
                return recs[0];
            }
        }

    }
}
DRapp
  • 47,638
  • 12
  • 72
  • 142