DEV Community

Cover image for Insert/read SQL-Server images with EF Core, Dapper and SqlClient
Karen Payne
Karen Payne

Posted on

Insert/read SQL-Server images with EF Core, Dapper and SqlClient

Working with EF Core/Dapper/SqlClient basics

Learn how to read and insert images into a SQL-Server database using Dapper, Entity Framework Core and SqlClient data provider.

For novice and even intermediate level developers working with images can be a daunting task simple because they either write code expecting it to immediately work with no regards to things like using the proper data types or copying pasting code from the Internet without changing much and expect the code to work.

To reach the main audiences three different approaches are used Dapper, EF Core and SqlClient data provider.

Using the proper data type

The developer with no experience with working with images will select Image verses varbinary(max) not realizing Image type most likely will be removed in future versions of SQL-Server. This means, do not use Image, instead use varbinary(max).

Column definition for code samples

table definition

Operations

The three methods used will both insert and read a single record.

What is important to know:

  • Dapper and EF Core require less hand written code
  • Dapper and SqlClient require decent knowledge of SQL syntax
  • EF Core, in this case were configured using EF Power Tools which means no manual configuration.
  • Which to use is a personal choice

Insert new record version 1

In the following samples a record is inserted but we do not get the new record key

Insert new record with SqlClient data provider

To insert a new record.

  • Create a connection object with a connection string
  • Create a command object
  • Add a single parameter
  • Open the connection
  • Execute the command
public static void InsertImage(byte[] imageBytes)
{
    var sql = "INSERT INTO [dbo].[Pictures1] ([Photo])  VALUES (@ByteArray)";

    using var cn = new SqlConnection(ConnectionString());
    using var cmd = new SqlCommand(sql, cn);

    cmd.Parameters.Add("@ByteArray", SqlDbType.VarBinary).Value = imageBytes;

    cn.Open();
    cmd.ExecuteNonQuery();
}
Enter fullscreen mode Exit fullscreen mode

Insert new record with Dapper

  • Create a connection object with a connection string
  • Add a single parameter
  • Execute the command
public static void InsertImage(byte[] imageBytes)
{

    var sql = "INSERT INTO [dbo].[Pictures1] ([Photo])  VALUES (@ByteArray)";

    using var cn = new SqlConnection(ConnectionString());
    var parameters = new { ByteArray = imageBytes};
    cn.Execute(sql, parameters);

}
Enter fullscreen mode Exit fullscreen mode

Insert new record with EF Core 7

  • Create an instance of the DbContext
  • Create an instance of the type
  • Save changes
public static void InsertImage(byte[] imageBytes)
{

    using var context = new Context();
    context.Add(new Pictures() { Photo = imageBytes });
    context.SaveChanges();

}
Enter fullscreen mode Exit fullscreen mode

Insert new record version 2

In the following samples a record is inserted with the new record primary key

SqlClient

We append SELECT CAST(scope_identity() AS int) to the insert and rather than ExecuteNonQuery use ExecuteScalar which returns an object, we cast to the same type as the primary key.

public static void InsertImage(byte[] imageBytes)
{
    var sql = 
        """
        INSERT INTO [dbo].[Pictures1] ([Photo])  VALUES (@ByteArray);
        SELECT CAST(scope_identity() AS int);
        """;

    using var cn = new SqlConnection(ConnectionString());
    using var cmd = new SqlCommand(sql, cn);

    cmd.Parameters.Add("@ByteArray", SqlDbType.VarBinary).Value = imageBytes;

    cn.Open();
    var key = (int)cmd.ExecuteScalar();
}
Enter fullscreen mode Exit fullscreen mode

Dapper

The following OUTPUT Inserted.Id is added to the SQL, note Id needs to match the primary key name.

Rather then Execute method we use ExecuteScalar which is basically the same as SqlClient.

public static void InsertImage(byte[] imageBytes)
{

    var sql = "INSERT INTO [dbo].[Pictures1] ([Photo]) OUTPUT Inserted.Id VALUES (@ByteArray)  ";

    using var cn = new SqlConnection(ConnectionString());
    var parameters = new { ByteArray = imageBytes};
    var key = (int)cn.ExecuteScalar(sql, parameters);

}
Enter fullscreen mode Exit fullscreen mode

EF Core

Simply move the Pictures object to a variable. After SaveChanges, photoContainer.Id will have the new primary key.

public static void InsertImage(byte[] imageBytes)
{

    using var context = new Context();

    var photoContainer = new Pictures() { Photo = imageBytes };
    context.Add(photoContainer);
    context.SaveChanges();

}
Enter fullscreen mode Exit fullscreen mode

Read an image

What you will notice is that between the three methods, they are practically the same.

With EF Core there are a few extra lines to keep in sync with the return type to matach the other two methods.

The SqlClient has several more lines of code than the other two methods.

SqlClient

public static (PhotoContainer container, bool success) ReadImage(int identifier)
{
    var photoContainer = new PhotoContainer() { Id = identifier };
    var sql = "SELECT id, Photo FROM dbo.Pictures1 WHERE dbo.Pictures1.id = @id;";

    using var cn = new SqlConnection(ConnectionString());
    using var cmd = new SqlCommand(sql, cn);

    cmd.Parameters.Add("@Id", SqlDbType.Int).Value = identifier;

    cn.Open();

    var reader = cmd.ExecuteReader();
    reader.Read();

    if (!reader.HasRows)
    {
        return (null, false);
    }

    var imageData = (byte[])reader[1];
    using (var ms = new MemoryStream(imageData, 0, imageData.Length))
    {
        ms.Write(imageData, 0, imageData.Length);
        photoContainer.Picture = Image.FromStream(ms, true);
    }

    return (photoContainer, true);
}
Enter fullscreen mode Exit fullscreen mode

Dapper

public static (PhotoContainer container, bool success) ReadImage(int identifier)
{

    var photoContainer = new PhotoContainer() { Id = identifier };
    var sql = "SELECT id, Photo FROM dbo.Pictures1 WHERE dbo.Pictures1.id = @id";

    using var cn = new SqlConnection(ConnectionString());
    var parameters = new { id = identifier };
    var container = cn.QueryFirstOrDefault<ImageContainer>(sql, parameters);

    if (container is null)
    {
        return (null, false);
    }

    var imageData = container.Photo;

    using (var ms = new MemoryStream(imageData, 0, imageData.Length))
    {
        ms.Write(imageData, 0, imageData.Length);
        photoContainer.Picture = Image.FromStream(ms, true);
    }

    return (photoContainer, true);
}
Enter fullscreen mode Exit fullscreen mode

EF Core

public static (PhotoContainer container, bool success) ReadImage(int identifier)
{
    var photoContainer = new PhotoContainer() { Id = identifier };
    using var context = new Context();

    var item = context.Pictures1.FirstOrDefault(item => item.Id == identifier);

    if (item is null)
    {
        return (null, false);
    }

    var imageData = item.Photo;

    using (var ms = new MemoryStream(imageData, 0, imageData.Length))
    {
        ms.Write(imageData, 0, imageData.Length);
        photoContainer.Picture = Image.FromStream(ms, true);
    }

    return (photoContainer, true);

}
Enter fullscreen mode Exit fullscreen mode

Note
There is no exception handling in any of the code as the code was written properly, this does not by any means to not implement exception handling in your code as many things can go wrong, so implement exception handling

Preparing the project to run

Using the script under the folder Scripts, create the database, create the single table and populate.

Why you a Windows Forms project?

Well the code had been done in an ASP.NET Core, Blazor, MAUI or unit test while using a Windows Forms project bypasses things such as securty issues and is very easy to read and understand.

Source code

Clone the following GitHub repository

Latest comments (0)