Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passing in QueryModel from re-linq #4417

Closed
biqas opened this issue Jan 27, 2016 · 14 comments
Closed

Passing in QueryModel from re-linq #4417

biqas opened this issue Jan 27, 2016 · 14 comments
Assignees
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Milestone

Comments

@biqas
Copy link

biqas commented Jan 27, 2016

Hi,

is it possible to to have an public method which is accepting QueryModel as input parameter instead of Expression parameter?

@biqas biqas changed the title Passing in QueryModel vom re-linq Passing in QueryModel from re-linq Jan 27, 2016
@rowanmiller
Copy link
Contributor

@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>();

@rowanmiller
Copy link
Contributor

Out of interest, what are you trying to do?

@biqas
Copy link
Author

biqas commented Feb 13, 2016

@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? :)

@rowanmiller
Copy link
Contributor

We discussed this more today, it's probably simpler to get the Database from the IServiceProvider and use the CompileQuery/CompileAsyncQuery methods.

@biqas
Copy link
Author

biqas commented Feb 16, 2016

Hi,

that sounds good, I will try it out in the next days.

@rowanmiller
Copy link
Contributor

Feel free to reopen if you hit issues

@biqas
Copy link
Author

biqas commented Mar 5, 2016

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?

@rowanmiller rowanmiller reopened this Mar 7, 2016
@rowanmiller rowanmiller added this to the 1.0.0 milestone Mar 7, 2016
@mikary
Copy link
Contributor

mikary commented Mar 7, 2016

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.

@biqas
Copy link
Author

biqas commented Mar 7, 2016

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)

@mikary
Copy link
Contributor

mikary commented Mar 8, 2016

I don't think I have the context to answer this question properly, clearing the milestone so this can go back to triage.

@mikary mikary removed this from the 1.0.0 milestone Mar 8, 2016
@rowanmiller
Copy link
Contributor

Interfaces are not supported yet, we'll probably handle this as part of #240 where you can tell us how to create instances of a type for a given interface.

@biqas biqas closed this as completed Mar 8, 2016
@biqas biqas reopened this Mar 15, 2016
@biqas
Copy link
Author

biqas commented Mar 15, 2016

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.IEnumerable1[System.Collections.Generic.IList1[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?

@smitpatel
Copy link
Member

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

@rowanmiller rowanmiller assigned smitpatel and unassigned mikary Mar 21, 2016
@rowanmiller rowanmiller added this to the 1.0.0 milestone Mar 21, 2016
@smitpatel
Copy link
Member

Feel free to re-open the issue if you encounter any further issues or have any questions.

@ajcvickers ajcvickers added closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-unknown labels Oct 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Projects
None yet
Development

No branches or pull requests

5 participants