{"id":22065602,"url":"https://github.com/karenpayneoregon/sqlserverinsertfiles","last_synced_at":"2025-05-13T01:24:53.391Z","repository":{"id":110840662,"uuid":"224490460","full_name":"karenpayneoregon/SqlServerInsertFiles","owner":"karenpayneoregon","description":"Provides code to insert files into SQL-Server database table","archived":false,"fork":false,"pushed_at":"2025-03-20T13:18:37.000Z","size":2694,"stargazers_count":10,"open_issues_count":0,"forks_count":5,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-01T05:34:44.562Z","etag":null,"topics":["csharp-code","sql","sqlserver"],"latest_commit_sha":null,"homepage":"https://dev.to/karenpayneoregon/c-insert-binary-files-into-sql-server-table-4laa","language":"TSQL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/karenpayneoregon.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-11-27T18:11:47.000Z","updated_at":"2025-03-23T18:55:53.000Z","dependencies_parsed_at":"2024-01-17T03:29:33.778Z","dependency_job_id":"36a28023-0db4-4f37-bcd9-ad954f8ebc23","html_url":"https://github.com/karenpayneoregon/SqlServerInsertFiles","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karenpayneoregon%2FSqlServerInsertFiles","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karenpayneoregon%2FSqlServerInsertFiles/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karenpayneoregon%2FSqlServerInsertFiles/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karenpayneoregon%2FSqlServerInsertFiles/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/karenpayneoregon","download_url":"https://codeload.github.com/karenpayneoregon/SqlServerInsertFiles/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253852167,"owners_count":21973871,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp-code","sql","sqlserver"],"created_at":"2024-11-30T19:20:14.688Z","updated_at":"2025-05-13T01:24:53.381Z","avatar_url":"https://github.com/karenpayneoregon.png","language":"TSQL","readme":"﻿# C# Insert binary files into SQL-Server table\n\n## Introduction\nLearn how to store any binary or non-binary file into a SQL-Server database table using C# and SqlClient  classes which is one option while the other option is using FILESTREAM. Which one to use can be a team decision, a DBA decision or dependent on amount and sizes of files. Here the focus will be on storing files in a column within a table.\n\n## Porting from old article\n\nThis article is a port from Microsoft TechNet which Karen wrote November 27 2019. Microsoft TechNet is going away thus the reason for this article.\n\nOriginal code was written in 4.5 and 4.7 framework which still exist, and two new projects have been added using NET9. To write the NET8 versions, new projects were created, old framework code added to the new class project then refactored the code. A console project was added to insert and read back a image file. It does not matter the file type, if a image works so will other file types.\n\n**Author’s opinion**, it depends on how many files will be stored along with file sizes and usage of images. For example, there is a table that represents categories for products with ten rows, one image per row, this is a decent reason for storing images in a database table. Before storing files in a database table read the Stackoverflow post and decide which is best, file system or database storage. \n\n## Code presented below\n\nNone of the NET9 code is shown, once the [repository](https://github.com/karenpayneoregon/SqlServerInsertFiles) is clone examine the code. NET8 code is basically the same as the framework code but better. For example `cmd.Parameters.AddWithValue` has been replaced with `cmd.Parameters.Add` and more refinements.\n\n\n## Source code\n\nClone the following GitHub [repository](https://github.com/karenpayneoregon/SqlServerInsertFiles). For the [NET8](https://dotnet.microsoft.com/en-us/download/visual-studio-sdks?cid=getdotnetsdk) core projects, this requires that NET9 is installed and using Microsoft [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/).\n\n## Storing files\n\nShould files be stored in a database or the file system?\n\nFor a great discussion, read the following [Stackoverflow post](https://stackoverflow.com/questions/3748/storing-images-in-db-yea-or-nay#3756).\n\n\n\n\n## Column type used for storing files\nThe column type which will be used is varbinary(MAX) which is best suited for this type of operation.\n\n## Storing a file\nCreate a class which has a method which accepts the path and filename, the second parameter is either the filename from the first parameter or perhaps a new filename. \n\n```csharp\npublic bool InsertFileSimple(string FilePath, string FileName, ref int NewIdentifier)\n```\n\n**Step 1**\n\nRead the file content into a byte array which will be used as a value for a command object parameter.\n\n```csharp\nbyte[] fileByes;\n \nusing (var stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read))\n{\n    using (var reader = new BinaryReader(stream))\n    {\n        fileByes = reader.ReadBytes((int)stream.Length);\n    }\n}\n```\n\n**Step 2**\n\nCreate a connection and command object, set the command text and parameters. The SQL INSERT statement differs from conventional SQL in that one parameter is passing a byte array to the query rather than string, numeric, date etc.\n\n```csharp\nusing (var cn = new SqlConnection() { ConnectionString = Default.ConnectionString })\n{\n    const string statement = \"INSERT INTO Table1 (FileContents,FileName) VALUES (@FileContents,@FileName);\" +\n                             \"SELECT CAST(scope_identity() AS int);\";\n \n    using (var cmd = new SqlCommand() { Connection = cn, CommandText = statement })\n    {\n        cmd.Parameters.Add(\"@FileContents\",\n            SqlDbType.VarBinary, fileByes.Length).Value = fileByes;\n \n        cmd.Parameters.AddWithValue(\"@FileName\", FileName);\n \n        try\n        {\n            cn.Open();\n \n            NewIdentifier = Convert.ToInt32(cmd.ExecuteScalar());\n            return true;\n \n        }\n        catch (Exception ex)\n        {\n            ExceptionMessage = ex.Message;\n            return false;\n        }\n    }\n}\n```\n\nNote that the SQL is actually two statements separated by a semi-colon. The second query is responsible for returning the new primary key for the newly added record.\n\n\n**Step 3**\n\nThis is actually the start, calling the method above and returning from the method call.\n\nSince InsertFileSimple returns a boolean this indicates success or failure. On success (returning true) identifier variable now contains the new primary key while if the operation failed identifier variable is invalid, check ExceptionMessage property for what happened.\n\n```csharp\nvar ops = new DataOperations();\nvar identifier = 0;\nvar fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, \"Dogma1.html\");\n \nif (ops.InsertFileSimple(fileName, \"Dogma1.html\", ref identifier))\n{\n    MessageBox.Show($\"Id is {identifier}\");\n}\nelse\n{\n    MessageBox.Show($\"Failed: {ops.ExceptionMessage}\");\n}\n```\n\n## Reading and writing to disk\n\nTo retrieve a file a SQL SELECT is used passing the primary key. The critical code is shown below, broken out from the method to return bytes and write to disk.\n\n- Get the ordinal index for the column containing byte array to the stored content.\n- The blob variable sets up the line of code below to perform the actual read which populates the variable block with the data read via reader.GetBytes.\n- Uses a FileStream to write the byte array to a file.\n\n```csharp\nvar reader = cmd.ExecuteReader();\n \nif (reader.HasRows)\n{\n    reader.Read();\n \n    // the blob column\n    var fieldOrdinal = reader.GetOrdinal(\"FileContents\");\n \n    var blob = new byte[(reader.GetBytes(\n        fieldOrdinal, 0,\n        null, 0,\n        int.MaxValue))];\n \n    reader.GetBytes(fieldOrdinal, 0, blob, 0, blob.Length);\n \n    using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))\n        fs.Write(blob, 0, blob.Length);\n \n}\n```\n\nEntire method to read from a table and write to a physical file. FileContents is the varbinary column for storing files.\n\n```csharp\npublic bool ReadFileFromDatabaseTableSimple(int Identifier, string fileName)\n{\n    using (var cn = new SqlConnection() { ConnectionString = Default.ConnectionString })\n    {\n        const string statement = \"SELECT id, [FileContents], FileName FROM Table1  WHERE id = @id;\";\n \n        using (var cmd = new SqlCommand() { Connection = cn, CommandText = statement})\n        {\n            cmd.Parameters.AddWithValue(\"@id\", Identifier);\n \n            try\n            {\n                cn.Open();\n \n            var reader = cmd.ExecuteReader();\n \n            if (reader.HasRows)\n            {\n                reader.Read();\n \n                // the blob column\n                var fieldOrdinal = reader.GetOrdinal(\"FileContents\");\n \n                var blob = new byte[(reader.GetBytes(\n                    fieldOrdinal, 0,\n                    null, 0,\n                    int.MaxValue))];\n \n                reader.GetBytes(fieldOrdinal, 0, blob, 0, blob.Length);\n \n                using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))\n                    fs.Write(blob, 0, blob.Length);\n \n            }\n \n                return true;\n \n            }\n \n            catch (Exception ex)\n            {\n                ExceptionMessage = ex.Message;\n                return false;\n            }\n        }\n    }\n}\n```\n\n## Storing more than a single file\n\nAlthough the code for storing a single file will work for storing many files code can get overly messy fast and error prone.  \n\n:stop_sign: The code sample presented uses inline methods which is not supported in some of the lower .NET Frameworks, if this is the case then extract the inline method to a private method in the same class.\n\nThe suggested pattern:\n\n- Wrap the executing code to perform the INSERT with a connection and command.\n- Command parameters will be created once and used later. In the single file insert parameters were created with AddWithValue which if used here would mean on each iteration the parameter collection would need to be cleared and then add parameters back in unlike the code below, create each parameter once.\n\n\nTo monitor progress in real time a delegate is used which the caller subscribes too, in this case a ListBox is populated in each iteration performing inserts.\n\n![Figure1](assets/Figure1.png)\n\nCode behind called in this case from a button click event.\n\n```csharp\nusing System;\nusing System.Collections.Generic;\nusing System.Data;\nusing System.Data.SqlClient;\nusing System.IO;\nusing static MultipleUpload.Properties.Settings;\n \nnamespace MultipleUpload\n{\n    public class DataOperations\n    {\n        public delegate void FileHandler(object sender, InsertFileArgs myArgs);\n        public event FileHandler OnLineHandler;\n \n        public string ExceptionMessage { get; set; }\n \n        /// \u003csummary\u003e\n        /// Takes a list of files and inserts them into a table with a delegate\n        /// which provides the caller information to see what's going on in real time.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"files\"\u003eList of files including their path\u003c/param\u003e\n        /// \u003creturns\u003eSuccess or failure\u003c/returns\u003e\n        public bool InsertFiles(List\u003cstring\u003e files)\n        {\n            /*\n             * in line method to get a file byte array suitable for inserting\n             * a new record into a table.\n             */\n            byte[] GetFileBytes(string fileName)\n            {\n                byte[] fileByes;\n \n                using (var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read))\n                {\n                    using (var reader = new BinaryReader(stream))\n                    {\n                        fileByes = reader.ReadBytes((int)stream.Length);\n                    }\n                }\n \n                return fileByes;\n            }\n \n            const string statement = \"INSERT INTO Table1 (FileContents,FileName)\" +\n                                     \" VALUES (@FileContents,@FileName);\" +\n                                     \"SELECT CAST(scope_identity() AS int);\";\n \n \n            using (var cn = new SqlConnection() {ConnectionString = Default.ConnectionString})\n            {\n                using (var cmd = new SqlCommand() {Connection = cn, CommandText = statement})\n                {\n                    cn.Open();\n \n                    cmd.Parameters.Add(\"@FileContents\", SqlDbType.VarBinary);\n                    cmd.Parameters.Add(\"@FileName\", SqlDbType.VarChar);\n \n                    /*\n                     * iterate the file array, insert file\n                     */\n                    foreach (var fileName in files)\n                    {\n                        var fileByes = GetFileBytes(fileName);\n                        cmd.Parameters[\"@FileContents\"].Size = fileByes.Length;\n                        cmd.Parameters[\"@FileContents\"].Value = fileByes;\n                        cmd.Parameters[\"@FileName\"].Value = Path.GetFileName(fileName);\n \n                        OnLineHandler(this, new InsertFileArgs(new[]\n                        {\n                            Convert.ToInt32(cmd.ExecuteScalar()).ToString(),\n                            fileName\n                        })) ;\n \n                    }\n                }\n \n            }\n \n            return true;\n        }\n \n    }\n \n    public class InsertFileArgs : EventArgs\n    {\n        protected string[] Line;\n \n        public InsertFileArgs(string[] sender)\n        {\n            Line = sender;\n        }\n        public string Identifier =\u003e Line[0];\n        public string FileName =\u003e Line[1];\n    }\n}\n```\n\n**Form code**\n\n```csharp\nusing System;\nusing System.IO;\nusing System.Linq;\nusing System.Windows.Forms;\n \nnamespace MultipleUpload\n{\n    public partial class Form1 : Form\n    {\n        public Form1()\n        {\n            InitializeComponent();\n \n            openFileDialog1.InitialDirectory = Path.Combine(AppDomain\n                .CurrentDomain.BaseDirectory, \"Files\");\n        }\n \n        /// \u003csummary\u003e\n        /// Ask for one or more files and insert into a table\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"sender\"\u003e\u003c/param\u003e\n        /// \u003cparam name=\"e\"\u003e\u003c/param\u003e\n        private void SelectFilesButton_Click(object sender, EventArgs e)\n        {\n \n            if (openFileDialog1.ShowDialog() == DialogResult.OK)\n            {\n                var ops = new DataOperations();\n                ops.OnLineHandler += OnLineHandler;\n                ops.InsertFiles(openFileDialog1.FileNames.ToList());\n            }\n        }\n        /// \u003csummary\u003e\n        /// Notify the user that records have been added via an event\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"sender\"\u003e\u003c/param\u003e\n        /// \u003cparam name=\"args\"\u003e\u003c/param\u003e\n        private void OnLineHandler(object sender, InsertFileArgs args)\n        {\n            listBox1.Items.Add($\"{args.Identifier} {args.FileName}\");\n            listBox1.SelectedIndex = listBox1.Items.Count - 1;\n        }\n \n    }\n}\n```\n\n## Connection strings (Framework 4.5)\n\nIn the supplied source code connection strings are stored under Settings tab for the property property page. Viewing connection string from app.config.\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\" ?\u003e\n\u003cconfiguration\u003e\n    \u003cconfigSections\u003e\n    \u003c/configSections\u003e\n    \u003cconnectionStrings\u003e\n        \u003cadd name=\"MultipleUpload.Properties.Settings.ConnectionString\"\n            connectionString=\"Data Source=.\\SQLEXPRESS;Initial Catalog=InsertImagesDatabase;Integrated Security=True\" /\u003e\n    \u003c/connectionStrings\u003e\n    \u003cstartup\u003e\n        \u003csupportedRuntime version=\"v4.0\" sku=\".NETFramework,Version=v4.7.2\" /\u003e\n    \u003c/startup\u003e\n\u003c/configuration\u003e\n```\n\nChange Data Source from .\\SQLEXPRESS if not using SQL-Express with a default installation in both projects. \n\n## Connection strings (NET8)\n\nConnection string is stored in appsettings.json and read via NuGet package [ConfigurationLibrary](https://www.nuget.org/packages/ConfigurationLibrary/1.0.6?_src=template). This library can have three environments\n\n```json\n﻿{\n  \"ConnectionsConfiguration\": {\n    \"ActiveEnvironment\": \"Development\",\n    \"Development\": \"Server=(localdb)\\\\mssqllocaldb;Database=EF.ShadowEntityCore;Trusted_Connection=True\",\n    \"Stage\": \"Stage connection string goes here\",\n    \"Production\": \"Prod connection string goes here\"\n  }\n}\n```\n\nBut for this code sample only Development is needed.\n\n```json\n{\n  \"ConnectionsConfiguration\": {\n    \"ActiveEnvironment\": \"Development\",\n    \"Development\": \"Data Source=.\\\\SQLEXPRESS;Initial Catalog=InsertImagesDatabase;Integrated Security=True;Encrypt=False\"\n  }\n}\n```\n\n## Creating the sample database\n\n\nThis is best done with SSMS (SQL-Server Management Studio)\n\n- Open SSMS\n- Create a new database named InsertImagesDatabase.\n- Open the script in the project BackendLibrary, folder DatabaseScripts and copy to a new query window in SSMS.\n- Run the script\n- Optionally create a backup of the database by right clicking the database, select Task, backup. Then after running the code samples you want to revert back to before running inserts do a restored from the database, task, restore database.\n\n\n## Running sample projects\n\nAfter downloading the source code, from solution explorer perform a solution rebuild. Run SingleFileUploadAndRead project first along with taking time to study the code which can be done by simply viewing or better yet setting breakpoints to go through code line by line is great for learning. Follow this up doing the same for the MultipleUpload project. \n\nWhile running these examples have SSMS open to view the results.\n\n## Summary\n\nSolid code has been presented to perform file insert to tables in SQL-Server database tables.","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarenpayneoregon%2Fsqlserverinsertfiles","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkarenpayneoregon%2Fsqlserverinsertfiles","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarenpayneoregon%2Fsqlserverinsertfiles/lists"}