gajo357
gajo357

Reputation: 978

SQLite connection strategies

I have a database that may be on the network drive. There are two things that I want to achieve:

  1. When the first user connects to it in read-only mode (he doesn't have a read-write access to the location, or the database is read-only), other users must use the read-only connection also (even if they have RW access).
  2. When the first user connects to it in RW mode, others can not connect to the database at all.

I'm using SQLite, and the concurrency should not be the problem, as the database should never be used by more than 10 people at the same time.

UPDATE: This is a sample that I'm trying to make work, so I could implement it in the program itself. Almost everything can be changed.

UPDATE: Now when I finally understood what @CL. was telling me, I made it work and this is the updated code.

using System.Diagnostics;
using System.Linq;
using System.IO;
using DbSample.Domain;
using DbSample.Infrastructure;
using NHibernate.Linq;
using NHibernate.Util;


namespace DbSample.Console
{
    class Program
    {
        static void Main(string[] args)
        {
            IDatabaseContext databaseContext = null;

            databaseContext = new SqliteDatabaseContext(args[1]);

        var connection = LockDB(args[1]);
        if (connection == null) return;

        var sessionFactory = databaseContext.CreateSessionFactory();
        if (sessionFactory != null)
        {

            int insertCount = 0;

            while (true) 
            {

                try
                {

                    using (var session = sessionFactory.OpenSession(connection))
                    {
                        string result;
                        session.FlushMode = NHibernate.FlushMode.Never;

                        var command = session.Connection.CreateCommand();

                        command.CommandText = "PRAGMA locking_mode=EXCLUSIVE";
                        command.ExecuteNonQuery();


                        using (var transaction = session.BeginTransaction(ReadCommited))
                        {
                            bool update = false;
                            bool delete = false;
                            bool read = false;
                            bool readall = false;
                            int op = 0;
                            System.Console.Write("\nMenu of the day:\n1: update\n2: delete\n3: read\n4: read all\n0: EXIT\n\nYour choice: ");
                            op = System.Convert.ToInt32(System.Console.ReadLine());
                            if (op == 1)
                                update = true;
                            else if (op == 2)
                                delete = true;
                            else if (op == 3)
                                read = true;
                            else if (op == 4)
                                readall = true;
                            else if (op == 0)
                                break;
                            else System.Console.WriteLine("Are you retarded? Can't you read?");




                            if (delete)
                            {
                                System.Console.Write("Enter the ID of the object to delete: ");
                                var objectToRemove = session.Get<MyObject>(System.Convert.ToInt32(System.Console.ReadLine()));

                                if (!(objectToRemove == null))
                                {
                                    session.Delete(objectToRemove);
                                    System.Console.WriteLine("Deleted {0}, ID: {1}", objectToRemove.MyName, objectToRemove.Id);
                                    deleteCount++;
                                }
                                else
                                    System.Console.WriteLine("\nObject not present in the database!\n");


                            }

                            else if (update)
                            {
                                System.Console.Write("How many objects to add/update? ");
                                int number = System.Convert.ToInt32(System.Console.ReadLine());
                                number += insertCount;
                                for (; insertCount < number; insertCount++)
                                {

                                    var myObject = session.Get<MyObject>(insertCount + 1);

                                    if (myObject == null)
                                    {
                                        myObject = new MyObject
                                            {
                                                MtName = "Object" + insertCount,
                                                IdLegacy = 0,
                                                                                           };
                                        session.Save(myObject);
                                        System.Console.WriteLine("Added {0}, ID: {1}", myObject.MyName, myObject.Id);
                                    }
                                    else
                                    {
                                        session.Update(myObject);
                                        System.Console.WriteLine("Updated {0}, ID: {1}", myObject.MyName, myObject.Id);
                                    }
                                }

                            }

                            else if (read)
                            {

                                System.Console.Write("Enter the ID of the object to read: ");
                                var objectToRead = session.Get<MyObject>(System.Convert.ToInt32(System.Console.ReadLine()));
                                if (!(objectToRead == null))
                                    System.Console.WriteLine("Got {0}, ID: {1}", objectToRead.MyName, objectToRead.Id);
                                else
                                    System.Console.WriteLine("\nObject not present in the database!\n");

                            }

                            else if (readall)
                            {

                                System.Console.Write("How many objects to read? ");
                                int number = System.Convert.ToInt32(System.Console.ReadLine());
                                for (int i = 0; i < number; i++)
                                {
                                    var objectToRead = session.Get<MyObject>(i + 1);
                                    if (!(objectToRead == null))
                                        System.Console.WriteLine("Got {0}, ID: {1}", objectToRead.MyName, objectToRead.Id);
                                    else
                                        System.Console.WriteLine("\nObject not present in the database! ID: {0}\n", i + 1);


                                }

                            }
                            update = false;
                            delete = false;
                            read = false;
                            readall = false;

                            transaction.Commit();
                        }
                    }    
                }
                catch (System.Exception e)
                {
                    throw e;
                }


            }
            sessionFactory.Close();
        }

    }

    private static SQLiteConnection LockDbNew(string database)
    {
        var fi = new FileInfo(database);
        if (!fi.Exists)
            return null;
        var builder = new SQLiteConnectionStringBuilder { DefaultTimeout = 1, DataSource = fi.FullName, Version = 3 };

        var connectionStr = builder.ToString();
        var connection = new SQLiteConnection(connectionStr) { DefaultTimeout = 1 };
        var cmd = new SQLiteCommand(connection);

        connection.Open();

        // try to get an exclusive lock on the database
        try
        {
            cmd.CommandText = "PRAGMA locking_mode = EXCLUSIVE; BEGIN EXCLUSIVE; COMMIT;";
            cmd.ExecuteNonQuery();
        }
        // if we can't get the exclusive lock, it could mean 3 things
        // 1: someone else has locked the database
        // 2: we don't have a write acces to the database location
        // 3: database itself is a read-only file
        // So, we try to connect as read-only
        catch (Exception)
        {
            // we try to set the SHARED lock
            try
            {
                // first we clear the locks
                cmd.CommandText = "PRAGMA locking_mode = NORMAL";
                cmd.ExecuteNonQuery();
                cmd.CommandText = "SELECT COUNT(*) FROM MyObject";
                cmd.ExecuteNonQuery();

                // then set the SHARED lock on the database
                cmd.CommandText = "PRAGMA locking_mode = EXCLUSIVE";
                cmd.ExecuteNonQuery();
                cmd.CommandText = "SELECT COUNT(*) FROM MyObject";
                cmd.ExecuteNonQuery();

                readOnly = true;
            }
            catch (Exception)
            {
                // if we can't set EXCLUSIVE nor SHARED lock, someone else has opened the DB in read-write mode and we can't connect at all
                connection.Close();
                return null;
            }

        } 
        return connection;
    }

 }

}

Upvotes: 3

Views: 2295

Answers (2)

CL.
CL.

Reputation: 180080

Set PRAGMA locking_mode=EXCLUSIVE to prevent SQLite from releasing its locks after a transaction ends.

Upvotes: 2

Ahmet Kakıcı
Ahmet Kakıcı

Reputation: 6404

I don't know if it can be done within db but in application; You can set a global variable (not sure if it's a web or desktop app) to check if anyone connected and he has a write access or not. After that you can check the other client's state.

Upvotes: 1

Related Questions