Skip to content

Slow Async Query of Byte[] in 2.2.1 #14498

@danjohnso

Description

@danjohnso

We are seeing 25-30x slower queries when using async to retrieve files stored as byte[] in our database. The SQL generated is fine, about the same as I would have handcrafted. I expected there would be some penalty to using async here, but not 25-30x.

Not aware if this happened before 2.2, didn't notice in our logs what these queries were performaing until more recently.

Steps to reproduce

Query files stored as byte[] in SQL Server using SingleAsync:

//case 1, 13 seconds to retrieve 10mb file
byte[] file = await _attachmentContext.AttachmentData.Where(x => x.Id == id).Select(x => x.Data).SingleAsync();

//case 2, 500ms to retrieve 10mb file
byte[] file = _attachmentContext.AttachmentData.Where(x => x.Id == id).Select(x => x.Data).Single();

//case 3, to rule out hardware issue, I used the dbContext to retrieve the Database connection and Dapper ORM to execute the same generated SQL, takes about 250ms 
connection = _attachmentContext.Database.GetDbConnection();
if (connection.State != ConnectionState.Open)
{
	connection.Open();
}
byte[] file = await connection.QuerySingleAsync<byte[]>("SELECT TOP 2 Data FROM AttachmentData WHERE Id = @id", new { id });

//case 4, Synchronous version of #3, takes about 175ms
connection = _attachmentContext.Database.GetDbConnection();
if (connection.State != ConnectionState.Open)
{
	connection.Open();
} 
byte[] file = connection.QuerySingle<byte[]>("SELECT TOP 2 Data FROM AttachmentData WHERE Id = @id", new { id });
//full entity and dbcontext
public class AttachmentData
{
	public Guid Id { get; set; }
	public DateTime Created { get; private set; }
	public Guid CreatedById { get; set; }

	private byte[] _data;
	public byte[] Data
	{
		get => _data;
		set
		{
			_data = value;
			if (value != null)
			{
				DataHash = value.Sha1();
				FileSize = value.LongLength;
			}
		}
	}

	public byte[] DataHash { get; private set; }
	public long FileSize { get; private set; }
	public DateTime Modified { get; set; }
	public Guid ModifiedById { get; set; }
	public byte[] RowVersion { get; set; }
}

internal static class AttachmentDataExtensions
{
	public static EntityTypeBuilder<AttachmentData> Map(this EntityTypeBuilder<AttachmentData> entity)
	{
		entity.ToTable(nameof(AttachmentData));
		entity.HasKey(x => x.Id);
		entity.Property(x => x.Id).ValueGeneratedOnAdd().HasDefaultValueSql("NEWID()");

		entity.Property(x => x.Created).ValueGeneratedOnAdd().HasDefaultValueSql("GETUTCDATE()");

		//HACK ef core does not support updates on DateTimes natively as of 2.1, requires a trigger to handle updates so we will just set manually
		entity.Property(x => x.Modified).ValueGeneratedOnAdd().HasDefaultValueSql("GETUTCDATE()");

		entity.Property(x => x.RowVersion).IsRowVersion();

		entity.Property(x => x.Data).IsRequired();
		entity.Property(x => x.DataHash).IsRequired();
		
		return entity;
	}
}


public class AttachmentContext : DbContext
{
	public AttachmentContext(DbContextOptions<AttachmentContext> options)
		: base(options)
	{
	}
	
	public DbSet<AttachmentData> AttachmentData { get; set; }

	protected override void OnModelCreating(ModelBuilder modelBuilder)
	{
		modelBuilder.Entity<AttachmentData>().Map();
		base.OnModelCreating(modelBuilder);
	}
}

Further technical details

EF Core version: 2.2.1
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating system: Win10/Server2016
IDE: Visual Studio 2017 15.9.5

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions