2

My model (generated with Entity Framework, bear in mind I've simplified it for the example) looks like this:

public class Person
{
    public string Name { get; set; }
    protected virtual ICollection<PersonHouseMapping> PersonHouseMappings { get; set; }

    public virtual IReadOnlyCollection<House> GetHouses(Func<House, bool> where = null)
    {
        var houses = PersonHouseMappings.Select(x => x.House);

        if (where != null)
            houses = houses.Where(where);

        return houses.ToList().AsReadOnly();
    }

    public void AddHouse(House house, DateTime movingDate)
    {
        PersonHouseMappings.Add(new PersonHouseMapping
        {
            Person = this,
            House = house,
            MovingDate = movingDate
        });
    }
}

public class House
{
    public int Number { get; set; }
    protected virtual ICollection<PersonHouseMapping> PersonHouseMappings { get; set; }
}

internal class PersonHouseMapping
{
    public virtual Person Person { get; set; }
    public virtual House House { get; set; }
    public DateTime MovingDate { get; set; }
}

There's a N:M relationship between Person and Houses, represented by the PersonHouseMapping entity. Normally EF would represent this with a navigation property, but because PersonHouseMapping has an extra field aside from the PKs of each entity part of the relation, it can't do it.

To abstract PersonHouseMapping from the users of my library, since it's not as clean as having the navigation property, I've made the ICollection protected and exposed the query/add functionality through methods that can be seen in the Person class. I don't want users to modify the returned collection, because they could think that adding a new House to the result of "GetHouses" would be saved to the DB, which isn't the case. To force them to call "AddHouse" I've made the return type an IReadOnlyCollection.

The problem is that when I want to construct a query for Person that includes conditions relative to its Houses, LINQ can't convert the expression to a valid Linq to Entities one. Let's say we wanted to query for all people who own a house with number 1:

dbContext.Persons.Where(p => p.GetHouses(h => h.Number == 1).Any()).ToList();

This wouldn't work, and throw the exception:

LINQ to Entities does not recognize the method 'System.Collections.Generic.IReadOnlyCollection`1[Namespace.House] GetHouses(System.Func`2[Namespace.House,System.Boolean])' method, and this method cannot be translated into a store expression.

Which makes sense. But how can I accomplish what I want while retaining the ability to perform these queries? Is there something like an IEnumerable that can't be instantiated into a List?

EDIT: I thought that by returning IEnumerable instead of IReadOnlyCollection the query would work (although it wouldn't solve my problem), but it doesn't. Why can't the expression be created?

EDIT 2: The reason why returning IEnumerable won't work either is that the problem isn't with the type, the problem is with having a method call in the middle of the LINQ expression. It needs to be directly translated to a store query, so no method calls :(

Thanks a lot

chuwik
  • 867
  • 1
  • 10
  • 22
  • looks like we need some custom Expression tree. – King King Nov 07 '13 at 17:29
  • I don't think you can call an arbitrary method on a CLR object as part of an EF query, as the query gets translated into SQL that executes in the database engine. – Mike Strobel Nov 07 '13 at 17:40
  • possible duplicate of [Entity Framework read only collections](http://stackoverflow.com/questions/11191103/entity-framework-read-only-collections) – ken2k Nov 07 '13 at 18:05

1 Answers1

0

The only way is to make mapping collection public:

public virtual ICollection<PersonHouseMapping> PersonHouseMappings { get; set; }

and re-write query:

dbContext.Persons.Where(p => p.PersonHouseMappings.Any(m => m.House.Number == 1)).ToList();

EF can't parse your method and build a part of expression tree from it.
Actually, I can't see any pros in your query.

Dennis
  • 37,026
  • 10
  • 82
  • 150
  • 1
    This doesn't match the _To abstract PersonHouseMapping from the users of my library, since it's not as clean as having the navigation property_ requirement of the OP – ken2k Nov 07 '13 at 18:04
  • 1
    @ken2k: when working with EF (and any other OR/M) you should understand, that there can be any limitations. If someone exposes EDM as a part of public API, then he exposes the same limitations to users of that API. If these limitations are unacceptable, then don't do this, make a domain model layer on top of EDM and expose it. – Dennis Nov 07 '13 at 18:11
  • What you propose will obviously solve the problem, it's what EF gives you by default. But I think it is nicer for the users to not have to create any "mapping" entities and deal with that; for a read query there may not be that much of a difference, but for adding or removing it is far nicer to just call the methods. – chuwik Nov 07 '13 at 18:23
  • By the way, the example query is just an example to illustrate the problem; in reality there's a PersonRepository with a GetMany call, where the "where clause" would go. – chuwik Nov 07 '13 at 18:24
  • @Dennis Of course there are limitations. My point was the OP already knows he can expose the collection for the query to be valid (this is the default EF behavior). I understand its question as "how do I do to **not** expose the collection?". The answer would be "you can't" or "build your own model on top of EF" – ken2k Nov 07 '13 at 18:24