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

Solution of Compute - Include - Decompile problem #53

Closed
wants to merge 1 commit into from
Closed

Solution of Compute - Include - Decompile problem #53

wants to merge 1 commit into from

Conversation

mayorovp
Copy link

@mayorovp mayorovp commented Jun 9, 2015

Issue #52

Added VisitConstant method to DecompileExpressionVisitor class. If constant is IQueryable it value's expression should be visited.

Added some logic to avoid infinite recursion (because EF DbSet expression is a value(ObjectQuery<...>).MergeAs(AppendData) where first constant is a internal DbSet query itself with same expression in).


Добавил метод VisitConstant в класс DecompileExpressionVisitor. Если константа является IQueryable - выражение, хранящееся в ее значении, также будет преобразовано.

Также добавил логику, необходимую для того, чтобы избежать вечной рекурсии. Это необходимо, потому что выражение, содержащееся в DbSet, определяется как value(ObjectQuery<...>).MergeAs(AppendData), где первая константа - это внутренний запрос самого DbSet, с точно таким же выражением внутри.

@hazzik
Copy link
Owner

hazzik commented Jun 15, 2015

@daveaglick @JonPSmith can you guys take a look?

@JonPSmith
Copy link
Collaborator

As I wrote the test harness then my comment is that Pavel's test does not follow the normal conventions. The test harness expects a test to a) run the test with LINQ without DelegateDecompiler (DD) to prove that it is possible and then b) run the same test, but using DD. This approach stops us chasing problems in DD which are really EF problems. There are plenty of examples to follow, e.g. Select Tests

Pavel.Mayorov's tests may be useful to him, but because they do not use the test harness its results won't be added to the automatically generated text documentation.

As to the fix then I will let @daveaglick comment on that :)

@mayorovp
Copy link
Author

Ok, let me explain my test case.

Look at this code:

db.EfParents.Where(p => ComputedSample()).Decompile().Load();

This code works as expected but does not load children. Let's try to add Include call:

db.EfParents.Include(p => p.Children).Where(p => ComputedSample()).Decompile().Load();

This code works but i cannot use this sequence in my project because of reasons. This code

db.EfParents.Where(p => ComputedSample()).Decompile().Include(p => p.Children).Load();

does not throw any exception but does not work as expected due to #38. The readme says: "Decompile method should be called after all ORM specific methods, otherwise it may not work". Ok, let's move Include right before Decompile:

db.EfParents.Where(p => ComputedSample()).Include(p => p.Children).Decompile().Load();

Oops, now we got an exception.

So, now readme has the mistake, right sentence is "Decompile method or any methods that references computed properties or functions should be called after all ORM specific methods, otherwise it may not work". In my pull request i try to fix behavior according to current version of documentation.

@mayorovp
Copy link
Author

PS ComputeDoubleIncludeDecompileNotFail is just a test of recursion.

@daveaglick
Copy link
Collaborator

I'll try and take a look at this tomorrow - I'm swamped today.

@daveaglick
Copy link
Collaborator

First off, I'm sure I speak for @hazzik when I say thanks for the PR!

I'd like to drop some reference information in here. I got curious what was really going on (why the heck are we getting a ConstantExpression when calling Include()?), and if checking for a ConstantExpression was solving the underlying issue or just a symptom. I traced the EF Include() call and found that the "problem" is happening in the internal ELinqQueryState class source here:

internal override ObjectQueryState Include<TElementType>(ObjectQuery<TElementType> sourceQuery, string includePath)
{
    var includeMethod = GetIncludeMethod(sourceQuery);
    Debug.Assert(includeMethod != null, "Unable to find ObjectQuery.Include method?");

    Expression includeCall = Expression.Call(
        Expression.Constant(sourceQuery), includeMethod, new Expression[] { Expression.Constant(includePath, typeof(string)) });
    ObjectQueryState retState = new ELinqQueryState(ElementType, ObjectContext, includeCall);
    ApplySettingsTo(retState);
    return retState;
}

Notice that it (indirectly) returns an Expression.Constant() - so that's where the constant expression is coming from. I wasn't aware EF would wrap queries like this, and it might not be the only situation in which it does. Therefore, I like the general approach of checking for IQueryable wrapped in a ConstantExpression and then decompiling the inner queryable - well done.

As for the code, it looks fine to me. The tests need some cleanup to use the harness that @JonPSmith developed, but I don't mind taking care of that after this is merged. FYI - this should also resolve #38. I'll add some additional tests for AsNoTracking() after merge as well, but some adhoc testing showed success (I don't think it wraps with a ConstantExpression anyway).

TL;DR: :shipit:

@mayorovp
Copy link
Author

No, this changes cannot resolve #38. Because EF use this code for third-party IQueryable providers:

    private static T CommonInclude<T>(T source, string path)
    {
        DebugCheck.NotNull((object)source);

        var includeMethod = source.GetType().GetRuntimeMethod(
            "Include", 
            p => p.IsPublic && !p.IsStatic,
            new[] { typeof(string) },
            new[] { typeof(IComparable) },
            new[] { typeof(ICloneable) },
            new[] { typeof(IComparable<string>) },
            new[] { typeof(IEnumerable<char>) },
            new[] { typeof(IEnumerable) },
            new[] { typeof(IEquatable<string>) },
            new[] { typeof(object) });

        if (includeMethod != null
            && typeof(T).IsAssignableFrom(includeMethod.ReturnType))
        {
            return (T)includeMethod.Invoke(source, new object[] { path });
        }
        return source;
    }

If DecompiledQueryable has no Include method, Include extension does nothing.

@hazzik
Copy link
Owner

hazzik commented Jun 18, 2015

@mayorovp can you change the test to use @JonPSmith framework?

@daveaglick
Copy link
Collaborator

@mayorovp Okay, I think I get what you're saying. This PR does fix the problem with crashing, but because we've wrapped the EF query provider by calling .Decompile(), when the wrapped .Include() call gets evaluated, EF runs the CommonInclude<T>(...) method above and instead of invoking the .Include() method of DbQuery<T> it just invokes...what? Are you thinking includeMethod would be null above? Do I understand your concern with #38?

I'm not sure this is the case, though. I ran your ComputeIncludeDecompileNotFail() test through a debugger to see what actually happens when we evaluate the .Include(). See the following screen shot (sorry for it being so wide):

image

Notice how when we evaluate the QueryableExtensions.Include<T>(...) method (in the right file), we still have a DbQuery<T> as the source. Because of that, we end up executing dbQuery.Include(path) instead of QueryableExtensions.CommonInclude<IQueryable<T>>(source, path). As far as I can tell, the include call goes to the DbQuery<T> as it should and we never even execute CommonInclude.

@mayorovp
Copy link
Author

Sorry, i misunderstood #38 because it was just a link to some comment in other PR. Yes, my changes resolves this issue:

Unfortunately moving Decompile to the end is not enough. I have a query with Where, OrderByDescending, Take and couple of Includes after it and moving Decompile to the end causes NotSupportedException exception so it looks like the Where expression isn't decompiled any more.

@hazzik
Copy link
Owner

hazzik commented Jun 18, 2015

@mayorovp can you create test for AsNoTracking, it might be fixed by your code.

@txavier
Copy link

txavier commented May 12, 2017

Any hope in getting this merged in?

@mayorovp
Copy link
Author

@txavier if someone [re]write tests...

Repository owner deleted a comment Nov 5, 2017
hazzik pushed a commit to txavier/DelegateDecompiler that referenced this pull request Nov 5, 2017
@hazzik hazzik changed the base branch from master to develop November 5, 2017 22:37
@hazzik
Copy link
Owner

hazzik commented Nov 5, 2017

Can you please enable Allow edits from maintainers option on this PR?

@mayorovp
Copy link
Author

mayorovp commented Nov 6, 2017

@hazzik enabled

@mayorovp mayorovp closed this Oct 10, 2018
@mayorovp mayorovp deleted the master branch October 10, 2018 11:53
@mayorovp mayorovp restored the master branch October 10, 2018 11:54
@mayorovp mayorovp reopened this Oct 10, 2018
@marcoz-tsn
Copy link

anyone please have news?

@mayorovp mayorovp closed this by deleting the head repository Sep 11, 2023
hazzik pushed a commit to txavier/DelegateDecompiler that referenced this pull request May 5, 2024
hazzik pushed a commit to txavier/DelegateDecompiler that referenced this pull request May 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants