Ocelot20
Ocelot20

Reputation: 10800

Entity Framework: Can't use "Contains" with property on another object

Sorry for the bad title...This one is probably best explained with an example:

void Main()
{
    IQueryable<ClassA> toLinkTo = context.ClassAs.Where(a => a.Name == "SomeName");

    var queryToExecute = context.ClassBs.Where(cb => toLinkTo.Select(ca => ca.Id).Contains(cb.Id));

    // This works.
    leafNodesWithExternalChildren.ToList();

    // This doesn't work.
    toLinkTo = new OtherClass(context).LinkedClassAs;
    leafNodesWithExternalChildren.ToList();
}

public class OtherClass
{
    private MyContext m_Context;

    public OtherClass(MyContext ctx)
    {
        this.m_Context = ctx;
    }

    public IQueryable<ClassA> LinkedClassAs
    {
        get
        {
            // Same as toLinkTo as it was originally declared above.
            return this.m_Context.ClassAs.Where(a => a.Name == "SomeName");
        }
    }
}

How come this works when toLinkTo is declared locally, but using the exact same IQueryable as a property on another object doesn't? The exception I get is:

Unable to create a constant value of type 'ClassA'. Only primitive types ('such as Int32, String, and Guid') are supported.

Thanks in advance.

Upvotes: 3

Views: 1602

Answers (1)

Slauma
Slauma

Reputation: 177163

Your second example works too - if you make it the first example:

IQueryable<ClassA> toLinkTo = new OtherClass(context).LinkedClassAs;
var queryToExecute = context.ClassBs.Where(cb => toLinkTo.Select(ca => ca.Id)
                                                         .Contains(cb.Id));

// Now, this works.
queryToExecute.ToList();

// Now, this doesn't work.
toLinkTo = context.ClassAs.Where(a => a.Name == "SomeName");
queryToExecute.ToList();

Somwhow, in the second try EF executes the toLinkTo query upfront and separately (as if it would append an AsEnumerable() to the query) to create a collection of objects in memory first. The queryToExecute doesn't work with this collection - as explained in @mellamokb's answer. In the first try the query is executed as a whole and the problem does not occur.

Both examples work if you create a new queryToExecute2 variable for the second example:

IQueryable<ClassA> toLinkTo = context.ClassAs.Where(a => a.Name == "SomeName");
var queryToExecute = context.ClassBs.Where(cb => toLinkTo.Select(ca => ca.Id)
                                                         .Contains(cb.Id));

// this works.
queryToExecute.ToList();

// And this works too.
toLinkTo = new OtherClass(context).LinkedClassAs;
var queryToExecute2 = context.ClassBs.Where(cb => toLinkTo.Select(ca => ca.Id)
                                                          .Contains(cb.Id));
queryToExecute2.ToList();

It has probably to do with the way how the expression tree for queryToExecute is built and how it uses the local variable toLinkTo or how EF evaluates the tree, but it's beyond my horizon to really understand or explain what's exactly going on.

Edit

Even if you use exactly the same query for toLinkTo the second try doesn't work:

IQueryable<ClassA> toLinkTo = context.ClassAs.Where(a => a.Name == "SomeName");
var queryToExecute = context.ClassBs.Where(cb => toLinkTo.Select(ca => ca.Id)
                                                         .Contains(cb.Id));

// this works.
queryToExecute.ToList();

// this doesn't work.
toLinkTo = context.ClassAs.Where(a => a.Name == "SomeName");
queryToExecute.ToList();

Upvotes: 3

Related Questions