-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Closed as not planned
Description
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
ascott18