ひとりも見捨てないことを、あきらめない

学校教育、社会教育、数学、技術家庭科、Youtube、EdTech、ICT、プログラミング、その他

SQLite データベースに、画像を保存する 01/10 金

 デスクトップで画像を処理して、それをデータベースに保存したり読み出したりというのは、基本的な事柄だと思います。しかし、現実には、検索してみても、ほとんどそういう記事が出てきません。

 自分で調べたり、他の詳しい方に質問したりして、だいたい次のような形にまとまりましたので、メモしておきます。

 

■環境

Windows10, Visual Studio 2019, C#, NET Framework 4.7, System.Data.SQLite

 

■NuGet

Visual Studio 2019 のプロジェクトから、NuGet パッケージの管理を選択し、「参照」で、「System.Data.SQLite」で検索すると、一番上に「System.Data.SQLite v1.0.112」が出てくるので、インスト―します。(2020/01/12 現在)

 

■ソース・ファイル

 次のようにプログラムします。このコードを、Form1.cs に丸ごと貼り付ければ、おそらく、動きます。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SQLite;
using System.Data.SQLite.EF6;
using System.Data.SQLite.Generic;
using System.Data.SQLite.Linq;
using System.Drawing;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Grph_SQLite
{
    public partial class Form1 : Form
    {
        Image im1, im2;

        public Form1()
        {
            InitializeComponent();

            using (var conn = new SQLiteConnection("DataSource=data.db"))
            {
                conn.Open();
                var comm = conn.CreateCommand();
                string sb = "CREATE TABLE IF NOT EXISTS mydata " +
                    " (name TEXT NOT NULL, face BLOB)";
                comm.CommandText = sb;
                comm.ExecuteNonQuery();
                conn.Close();
            }

            #region 画像を切り取って、im1, im2 に格納し、表示する。

            PictureBox pic = new PictureBox();
            pic.Location = new Point(10, 10);
            pic.Size = new Size(500, 300);
            pic.Image = Image.FromFile("data.png");
            Controls.Add(pic);

            // 元のイメージを切り取って、im1 に貼り付ける
            im1 = new Bitmap(300, 70);
            Graphics g = Graphics.FromImage(im1);
            Rectangle srcRect = new Rectangle(90, 80, 300, 70);
            Rectangle desRect = new Rectangle(0, 0, 300, 70);
            g.DrawImage(pic.Image, desRect, srcRect, GraphicsUnit.Pixel);
            g.Dispose();

            PictureBox pic1 = new PictureBox();
            pic1.Location = new Point(10, 350);
            pic1.Size = new Size(300, 70);
            pic1.Image = im1;
            Controls.Add(pic1);

            // 元のイメージを切り取って、im2 に貼り付ける
            im2 = new Bitmap(300, 70);
            g = Graphics.FromImage(im2);
            srcRect = new Rectangle(90, 180, 300, 70);
            desRect = new Rectangle(0, 0, 300, 70);
            g.DrawImage(pic.Image, desRect, srcRect, GraphicsUnit.Pixel);
            g.Dispose();

            PictureBox pic2 = new PictureBox();
            pic2.Location = new Point(10, 450);
            pic2.Size = new Size(300, 70);
            pic2.Image = im2;
            Controls.Add(pic2);

            // 以上で、im1, im2 に画像データが格納されたことが確認できた。

            #endregion

            #region 画像をデータを、データベースに登録する。

            using (var conn = new SQLiteConnection("DataSource=data.db"))
            {
                conn.Open();

                var comm = conn.CreateCommand();
                string str = "insert into mydata(name) values('visual studio1');";
                comm.CommandText = str;
                comm.ExecuteNonQuery();

                str = "insert into mydata(name) values('visual studio2');";
                comm.CommandText = str;
                comm.ExecuteNonQuery();

                // こうすればできます
                comm.CommandText = "insert into mydata(name,face) values ('visual studio3',@face);";
                ImageConverter converter = new ImageConverter();
                byte[] fdata = (byte[])converter.ConvertTo(im2, typeof(byte[]));
                var param = new SQLiteParameter("@face", System.Data.DbType.Binary);
                param.Value = fdata;
                comm.Parameters.Add(param);
                comm.ExecuteNonQuery();

                conn.Close();
            }

            #endregion
        }
    }
}

 つぎに、実際のプログラムでは、このような保存作業を、一度に 10000回程度実施する予定ですので、for 文を用いて、10000 回実施してみました。

using (var conn = new SQLiteConnection("DataSource=data.db"))
{
    conn.Open();
    SQLiteCommand cmd = conn.CreateCommand();
    ImageConverter converter = new ImageConverter();
    byte[] fdata;
    SQLiteParameter param;

    for (int i=0; i < 10000; i++)
    {
        /*
        cmd.CommandText = "insert into mydata(name) values (@ID)";
        cmd.Parameters.Add(new SQLiteParameter("@ID", "visual studio4"));
        cmd.ExecuteNonQuery();
        */

        cmd.CommandText = "insert into mydata(name,face) values ('visual studio5',@face);";
        fdata = (byte[])converter.ConvertTo(im2, typeof(byte[]));
        param = new SQLiteParameter("@face", System.Data.DbType.Binary);
        param.Value = fdata;
        cmd.Parameters.Add(param);
        cmd.ExecuteNonQuery();
    }

    conn.Close();
}

ところが、100000回、作業を行うのに5分から7分程度必要で、とても実用に堪えないことがわかりました。このことについては、あちこちのページで「トランザクション処理が原因で、非常に速度が遅くなる」と書いてありましたので、実際に作業の前後にトランザクションに関する命令を追加しました。

    var ts = conn.BeginTransaction();
<途中の処理>
ts.Commit();

この2行を追加したプログラムは、次のとおりです。

using (var conn = new SQLiteConnection("DataSource=data.db"))
{
    conn.Open();
    var ts = conn.BeginTransaction();
    SQLiteCommand cmd = conn.CreateCommand();
    ImageConverter converter = new ImageConverter();
    byte[] fdata;
    SQLiteParameter param;
    for (int i=0; i < 10000; i++)
    {
        /*
        cmd.CommandText = "insert into mydata(name) values (@ID)";
        cmd.Parameters.Add(new SQLiteParameter("@ID", "visual studio4"));
        cmd.ExecuteNonQuery();
        */

        cmd.CommandText = "insert into mydata(name,face) values ('visual studio5',@face);";
        fdata = (byte[])converter.ConvertTo(im2, typeof(byte[]));
        param = new SQLiteParameter("@face", System.Data.DbType.Binary);
        param.Value = fdata;
        cmd.Parameters.Add(param);
        cmd.ExecuteNonQuery();

    }

    ts.Commit();
    conn.Close();
}

今度は、作業は数秒で終了してしまいました。

f:id:takase_hiroyuki:20191123160855p:plain