Closed biqas closed 8 years ago
@biqas EntityQueryModelVisitor
is the type you are after. You can get a factory for this type from the IServiceProvider
associated with a context.
var serviceProvider = context.GetInfrastructure<IServiceProvider>();
var factory = serviceProvider.GetService<IEntityQueryModelVisitorFactory>();
Out of interest, what are you trying to do?
@rowanmiller Hi I'm trying to build out a library which is able to compose, configure and query services and for example for the querying topic I choose first raw expression trees as an input parameter contract but switched over to the QuerModel from re-linq. So in that case it would be great if EF had an public API to pass directly QueryModel's (from re-linq, or maybe EF-Team is abstracting them) this could open much more preprocessing of queries in a disconnected way.
Basically in one of the steps I'm analyzing amd striping away some partial linq statements and adding service specific linq statements and it is much easier to do so in re-linq.
Currently the process is: Expression is created, QueryModel is created based on the expression, modified QueryModel is converted back to expression, expression passed to EF, EF creates QueryModel based on the expression, QueryModel creates target-statements (SQL-statements).
Hope this makes it some how understandable? :)
We discussed this more today, it's probably simpler to get the Database
from the IServiceProvider
and use the CompileQuery
/CompileAsyncQuery
methods.
Hi,
that sounds good, I will try it out in the next days.
Feel free to reopen if you hit issues
Hi, I had finally time to test the proposed methods (CompileQuery), but ran into some issues.
using (var db = new BloggingContext())
{
var serviceProvider = ((IInfrastructure<IServiceProvider>)db).Instance;
var database = (IDatabase)serviceProvider.GetService(typeof(IDatabase));
var queryContextFactory = (IQueryContextFactory)serviceProvider.GetService(typeof(IQueryContextFactory));
var query = new EnumerableQuery<Blog>(new List<Blog>())
.Where(x => x.Url != "");
var queryModel = QueryParser.CreateDefault().GetParsedQuery(query.Expression);
var fromExpression = Expression.Constant(db.Blogs);
queryModel.MainFromClause.FromExpression = fromExpression;
var compileQueryFn = database.CompileQuery<Blog>(@queryModel);
var result = compileQueryFn(queryContextFactory.Create()).ToList();
}
I'm getting the following error:
"A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe."
BloggingContext is taken from the EF documentation.
Question is, is this the intended behavior if so, how can I get around it?
It looks like the MainFromClause is a constant expression with a value of InternalDbSet<Blog>
(implementation type of DbSet<Blog>
), but we are expecting an EntityQueryable<Blog>
. The quick fix for this would be to replace:
var fromExpression = Expression.Constant(db.Blogs);
with:
var fromExpression = Expression.Constant(new EntityQueryable<Blog>(db.GetService<IAsyncQueryProvider>()));
The concurrency exception was basically a reentry test that was triggering because the query enumerated the inner query (db.Blogs
) in the middle of enumerating the actual query.
I don't know if it will work for your scenario, but you might try replacing the IQueryCompiler
service. This service is the one that is responsible for preprocessing the expression tree and compiling the QueryModel
.
Hi, appreciate the quick investigation. It seems to work to an extend for my scenario.
The problem wich is left over I think is not directly related to this issue, which is:
public interface IBlog
{
int BlogId { get; set; }
string Url { get; set; }
}
public class Blog : IBlog
{
public int BlogId { get; set; }
public string Url { get; set; }
public List<Post> Posts { get; set; }
}
Note the interface usage.
using (var db = new BloggingContext())
{
var serviceProvider = ((IInfrastructure<IServiceProvider>)db).Instance;
var database = (IDatabase)serviceProvider.GetService(typeof(IDatabase));
var queryContextFactory = (IQueryContextFactory)serviceProvider.GetService(typeof(IQueryContextFactory));
var query = new EnumerableQuery<IBlog>(new List<IBlog>())
.Where(x => x.Url != "");
var queryModel = QueryParser.CreateDefault().GetParsedQuery(query.Expression);
var queryProvider = (IAsyncQueryProvider)serviceProvider.GetService(typeof(IAsyncQueryProvider));
var fromExpression = Expression.Constant(new EntityQueryable<IBlog>(queryProvider));
queryModel.MainFromClause.FromExpression = fromExpression;
var compileQueryFn = database.CompileQuery<Blog>(@queryModel);
var result = compileQueryFn(queryContextFactory.Create()).ToList();
}
So far I know EF is currently not supporting interface definitions, I think I have to rewrite the places where interfaces are used within the QueryModel.
Out of interest, why is EF not supporting or making use of structural matching for mappings and translating materialisation to types? (And of course then interfaces could be a good fit)
I don't think I have the context to answer this question properly, clearing the milestone so this can go back to triage.
Interfaces are not supported yet, we'll probably handle this as part of https://github.com/aspnet/EntityFramework/issues/240 where you can tell us how to create instances of a type for a given interface.
Hi I faced one issue
using (var db = new BloggingContext())
{
var serviceProvider = ((IInfrastructure<IServiceProvider>)db).Instance;
var database = (IDatabase)serviceProvider.GetService(typeof(IDatabase));
var queryContextFactory = (IQueryContextFactory)serviceProvider.GetService(typeof(IQueryContextFactory));
var query = new EnumerableQuery<IBlog>(new List<IBlog>())
.Where(x => x.Url != "")
.Select(x => x,Posts);
var queryModel = QueryParser.CreateDefault().GetParsedQuery(query.Expression);
var queryProvider = (IAsyncQueryProvider)serviceProvider.GetService(typeof(IAsyncQueryProvider));
var fromExpression = Expression.Constant(new EntityQueryable<IBlog>(queryProvider));
queryModel.MainFromClause.FromExpression = fromExpression;
var compileQueryFn = database.CompileQuery<Blog>(@queryModel);
var result = compileQueryFn(queryContextFactory.Create()).ToList();
}
.Select(x => x,Posts);
if I change the projection to posts, then the call to
database.CompileQuery<Blog>(@queryModel);
throws an error
Additional information: Expression of type 'System.Collections.Generic.IEnumerable
1[System.Collections.Generic.IList
1[EFGetStarted.ConsoleApp.Post]]' cannot be used for return type 'System.Collections.Generic.IEnumerable`1[EFGetStarted.ConsoleApp.Post]'
Also tried to change the generic type parameter to:
database.CompileQuery<Post>(@queryModel);
but no success.
Any recommendations?
Not sure exactly what you are trying to query but following code works (at least not throwing error. I don't have sample data to verify results)
using (var db = new BloggingContext())
{
var serviceProvider = ((IInfrastructure<IServiceProvider>)db).Instance;
var database = (IDatabase)serviceProvider.GetService(typeof(IDatabase));
var queryContextFactory = (IQueryContextFactory)serviceProvider.GetService(typeof(IQueryContextFactory));
var query = new EnumerableQuery<Blog>(new List<Blog>())
.Where(x => x.Url != "")
.SelectMany(x => x.Posts);
var queryModel = QueryParser.CreateDefault().GetParsedQuery(query.Expression);
var queryProvider = (IAsyncQueryProvider)serviceProvider.GetService(typeof(IAsyncQueryProvider));
var fromExpression = Expression.Constant(new EntityQueryable<Blog>(queryProvider));
queryModel.MainFromClause.FromExpression = fromExpression;
var compileQueryFn = database.CompileQuery<Post>(@queryModel);
var result = compileQueryFn(queryContextFactory.Create()).ToList();
}
Changes made:
.Select(x => x,Posts);
This just throw compilation error on me. ,
is some invalid syntax.
I converted it to x.Posts
for which I had to change IBlog
to Blog
so that I can access the property inside Select.
After above changes I hit the same exception as yours.
The issue is Select(x => x.Posts)
which returns List<Post>
therefore return type of query becomes Enumerable<List<Post>>
while the return type of CompileQuery<Post>
is Enumerable<Post>
.
If you are looking to select posts in all blogs then you should use SelectMany
Feel free to re-open the issue if you encounter any further issues or have any questions.
Hi,
is it possible to to have an public method which is accepting QueryModel as input parameter instead of Expression parameter?