简体   繁体   中英

IReadOnlyCollection part of a LINQ query

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM