问题
I ran into one interesting thing in EF. If we get child entity using base entity, loading entities takes more time. My model looks like this:
public abstract class BaseDocument
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public abstract class ComplexDocument : BaseDocument
{
public string AuthorName { get; set; }
}
public abstract class SimpleDocument : BaseDocument
{
public int Level { get; set; }
}
public abstract class OfficeDocument : ComplexDocument
{
public string OfficeName { get; set; }
}
public abstract class ClassDocument : SimpleDocument
{
public string HeadName { get; set; }
}
public class WordDocument : OfficeDocument
{
public int PagesCount { get; set; }
}
public class ExcelDocument : OfficeDocument
{
public int SheetsCount { get; set; }
}
public class TextDocument : ClassDocument
{
public int LinesCount { get; set; }
}
I am using the TPT approach. Here is the inheritance tree Here is my context class:
public class Context : DbContext
{
public Context() : base(@"Server=(localdb)\MSSQLLocalDB;Database=EFSIX;Trusted_Connection=True;")
{
Database.CreateIfNotExists();
}
public DbSet<BaseDocument> BaseDocuments { get; set; }
public DbSet<ComplexDocument> ComplexDocuments { get; set; }
public DbSet<SimpleDocument> SimpleDocuments { get; set; }
public DbSet<OfficeDocument> OfficeDocuments { get; set; }
public DbSet<ClassDocument> ClassDocuments { get; set; }
public DbSet<ExcelDocument> ExcelDocuments { get; set; }
public DbSet<WordDocument> WordDocuments { get; set; }
public DbSet<TextDocument> TextDocuments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BaseDocument>().ToTable("BaseDocuments");
modelBuilder.Entity<ComplexDocument>().ToTable("ComplexDocuments");
modelBuilder.Entity<SimpleDocument>().ToTable("SimpleDocuments");
modelBuilder.Entity<OfficeDocument>().ToTable("OfficeDocuments");
modelBuilder.Entity<ExcelDocument>().ToTable("ExcelDocuments");
modelBuilder.Entity<WordDocument>().ToTable("WordDocuments");
modelBuilder.Entity<ClassDocument>().ToTable("ClassDocuments");
modelBuilder.Entity<TextDocument>().ToTable("TextDocuments");
}
public IQueryable<T> GetEntities<T>() where T : class
{
return Set<T>();
}
}
I'm creating some data:
static void CreateTestData()
{
using (Context context = new Context())
{
for (int i = 0; i < 20; i++)
{
ExcelDocument excel = new ExcelDocument()
{
Id = Guid.NewGuid(),
AuthorName = $"ExcelAuthor{i}",
Name = $"Excel{i}",
OfficeName = $"ExcelOffice{i}",
SheetsCount = (i + 1) * 10
};
context.ExcelDocuments.Add(excel);
WordDocument word = new WordDocument()
{
Id = Guid.NewGuid(),
AuthorName = $"WordAuthor{i}",
Name = $"Word{i}",
OfficeName = $"WordOffice{i}",
PagesCount = (i + 2) * 10
};
context.WordDocuments.Add(word);
TextDocument text = new TextDocument()
{
Id = Guid.NewGuid(),
Name = $"Text{i}",
LinesCount = (i + 3) * 10,
HeadName = $"Head{i}",
Level = i + 5
};
context.TextDocuments.Add(text);
}
context.SaveChanges();
}
}
I made some two methods for getting WordDocument from db. One of them using BaseDocument and another one using WordDocument. Both returns 20 instances of WordDocument:
static long ReadBaseDoc()
{
using (Context context = new Context())
{
var words= context.GetEntities<BaseDocument>().Where(e => e.Name.StartsWith("Word"));
Stopwatch stopwatch = Stopwatch.StartNew();
var instacnes = excel.ToList();
stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}
}
static long ReadWordDoc()
{
using (Context context = new Context())
{
var words = context.GetEntities<WordDocument>().Where(e => e.Name.StartsWith("Word"));
Stopwatch stopwatch = Stopwatch.StartNew();
var instacnes = words.ToList();
stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}
}
I tested moth method separately, several times, in average method ReadWordDoc takes 25ms and method ReadBaseDoc takes 52ms (instances are the same ).
It's not too big problem now, but when we have complex inheritance it takes more than 1 second. I created 10 classes and inherited from BaseDocument. After that I executed ReadBaseDoc and ReadWordDoc methods. ReadWordDoc took 25ms and ReadBaseDoc took 1023ms. Instances are the same, why ReadBaseDoc takes more time? What is the better way to avoid this kind of problems in EF?
回答1:
Take a look here. There are ways to make EF faster, but in those complex scenarios ORM just creates more problems than it solves.
One way in your case would be to try to change the inheritance to TablePerType, MAYBE it will be a little bit faster.
Other way would be to locate the slow request and use Dapper for them - it will be much faster.
Last way would be to create a Repository with live cache that loads the full database into memory and keeps it up to date - this should be a singleton in an app. If you have more than one app using the same database, you need to hookup data change triggers.
In general, I would say for slow (and relatively simple) queries like yours, use Dapper + AutoMapper. Keep EF so that your database stays synchronized with your classes, but do not rely on it for queries.
If you really want to stick to ORM, I think you need to switch nHibernate. Haven't try it myself, but form what I read, it is superior in almost every possible way, that includes performance and startup time.
来源:https://stackoverflow.com/questions/56946573/loading-instance-of-entity-takes-more-than-1-second