Entity Framework Core – Part 5

Entity Framework Core – Part 5

This will be the fifth post in a series of posts about bringing the features that were present in Entity Framework pre-Core into EF Core. The others are:

Part 1: Introduction, Find, Getting an Entity’s Id Programmatically, Reload, Local, Evict
Part 2: Explicit Loading
Part 3: Validations
Part 4: Conventions

This time I’m going to talk about something that is often requested: how can I get the SQL string for a LINQ query? If you remember, in the pre-Core days you had to do some reflection in order to get the underlying ObjectQuery and then call its ToTraceString method. Now, things are very different, although I may say, still rather tricky!

  • private static readonly TypeInfo QueryCompilerTypeInfo = typeof(QueryCompiler).GetTypeInfo();
  • private static readonly FieldInfo QueryCompilerField = typeof(EntityQueryProvider).GetTypeInfo().DeclaredFields.First(x => x.Name == “_queryCompiler”);
  • private static readonly PropertyInfo NodeTypeProviderField = QueryCompilerTypeInfo.DeclaredProperties.Single(x => x.Name == “NodeTypeProvider”);
  • private static readonly MethodInfo CreateQueryParserMethod = QueryCompilerTypeInfo.DeclaredMethods.First(x => x.Name == “CreateQueryParser”);
  • private static readonly FieldInfo DataBaseField = QueryCompilerTypeInfo.DeclaredFields.Single(x => x.Name == “_database”);
  • private static readonly FieldInfo QueryCompilationContextFactoryField = typeof(Database).GetTypeInfo().DeclaredFields.Single(x => x.Name == “_queryCompilationContextFactory”);
  • public static string ToSql(this IQueryable query) where TEntity : class
  • {
  • if (!(query is EntityQueryable) && !(query is InternalDbSet))
  • {
  • throw new ArgumentException(“Invalid query”);
  • }
  • var queryCompiler = (IQueryCompiler) QueryCompilerField.GetValue(query.Provider);
  • var nodeTypeProvider = (INodeTypeProvider) NodeTypeProviderField.GetValue(queryCompiler);
  • var parser = (IQueryParser) CreateQueryParserMethod.Invoke(queryCompiler, new object[] { nodeTypeProvider });
  • var queryModel = parser.GetParsedQuery(query.Expression);
  • var database = DataBaseField.GetValue(queryCompiler);
  • var queryCompilationContextFactory = (IQueryCompilationContextFactory)
  • ueryCompilationContextFactoryField.GetValue(database);
  • var queryCompilationContext = queryCompilationContextFactory.Create(false);
  • var modelVisitor = (RelationalQueryModelVisitor) queryCompilationContext.CreateQueryModelVisitor();
  • modelVisitor.CreateQueryExecutor(queryModel);
  • var sql = modelVisitor.Queries.First().ToString();
  • return sql;
  • }

You can see that it needs some reflection, meaning, things *may* break on a future version. I cached all of the fields to make the access faster in subsequent calls. For the time being, it works perfectly:

  • var sql1 = ctx.Blogs.ToSql();
  • var sql2 = ctx
  • .Blogs
  • .Where(b => b.CreationDate.Year == 2017)
  • .ToSql();

About the author

admin administrator

Leave a Reply