Операторы Dynamic Include для активной загрузки в запросе — EF 4.3.1

У меня есть этот метод:

public CampaignCreative GetCampaignCreativeById(int id)
        {
            using (var db = GetContext())
            {
                return db.CampaignCreatives
                    .Include("Placement")
                    .Include("CreativeType")                    
                    .Include("Campaign")
                    .Include("Campaign.Handshake")
                    .Include("Campaign.Handshake.Agency")
                    .Include("Campaign.Product")
                    .AsNoTracking()
                    .Where(x => x.Id.Equals(id)).FirstOrDefault();
            }
        }

Я хотел бы сделать список включений динамическим. Я пытался:

public CampaignCreative GetCampaignCreativeById(int id, string[] includes)
        {
            using (var db = GetContext())
            {
                var query = db.CampaignCreatives;

                foreach (string include in includes)
                {
                    query = query.Include(include);
                }

                return query.AsNoTracking()
                    .Where(x => x.Id.Equals(id)).FirstOrDefault();                    
            }
        }

Но он не скомпилировался. Я получил эту ошибку:

Невозможно неявно преобразовать тип System.Data.Entity.Infrastructure.DbQuery в System.Data.Entity.DbSet. Существует явное преобразование (вам не хватает приведения?)

Кто-нибудь знает, как сделать список включений динамическим?

Спасибо


person LeoD    schedule 03.04.2012    source источник
comment
Я сделал плагин, который делает именно это, вот ссылка codeproject. com/Советы/1205294/   -  person Alen.Toma    schedule 11.09.2017


Ответы (4)


Сделайте переменную query доступной для запросов:

public CampaignCreative GetCampaignCreativeById(int id, string[] includes)
{
    using (var db = GetContext())
    {
        var query = db.CampaignCreatives.AsQueryable();
        foreach (string include in includes)
        {
            query = query.Include(include);
        }

        return query
            .AsNoTracking()
            .Where(x => x.Id.Equals(id))
            .FirstOrDefault();                    
    }
}
person Darin Dimitrov    schedule 03.04.2012
comment
Я не понимаю. IQueryable не имеет метода Include. Это не компилируется. - person dudeNumber4; 12.03.2014
comment
В некоторых проектах вы получите ошибку компиляции с этим решением. Добавить с помощью System.Data.Entity; для доступа к методу расширения Include в IQueryable - person erstaples; 08.08.2014
comment
Это не работает для EF4.0. Отсутствует Включить расширение - person Davut Gürbüz; 12.01.2015
comment
IQuerable не имеет расширения Include для EF4.0. Для EF4.0 вы наоборот приводите IQuerable‹T› к ObjectQuery‹T›. Затем вы можете включить детали. - person Davut Gürbüz; 12.01.2015

Мне больше нравится нестроковый выразительный способ определения включений. В основном потому, что он не полагается на магические строки.

Для примера кода это будет выглядеть примерно так:

public CampaignCreative GetCampaignCreativeById(int id) {
    using (var db = GetContext()) {
        return db.CampaignCreatives
            .Include(cc => cc.Placement)
            .Include(cc => cc.CreativeType)                    
            .Include(cc => cc.Campaign.Select(c => 
                 c.Handshake.Select(h => h.Agency)))
            .Include(cc => cc.Campaign.Select(c => c.Product)
            .AsNoTracking()
            .Where(x => x.Id.Equals(id))
            .FirstOrDefault();
    }
}

И чтобы сделать их динамичными, вот как вы это делаете:

public CampaignCreative GetCampaignCreativeById(
    int id, 
    params Expression<Func<T, object>>[] includes
) {
    using (var db = GetContext()) {
        var query = db.CampaignCreatives;
        return includes
            .Aggregate(
                query.AsQueryable(), 
                (current, include) => current.Include(include)
            )
            .FirstOrDefault(e => e.Id == id);
    }
}

Который используется так:

var c = dataService.GetCampaignCreativeById(
     1, 
     cc => cc.Placement, 
     cc => cc.CreativeType, 
     cc => cc.Campaign.Select(c => c.Handshake.Select(h => h.Agency)),
     cc => cc.Campaign.Select(c => c.Product
);
person Mikael Östberg    schedule 04.10.2013
comment
Я сказал это выше, но я скажу это здесь, если кто-то пропустил это: в некоторых проектах вы получите ошибку компиляции с этим решением. Добавить с помощью System.Data.Entity; для доступа к методу расширения Include в IQueryable. - person erstaples; 08.08.2014
comment
Этот ответ должен был быть проверен. Я нашел его чрезвычайно полезным, когда речь идет о строго типизированных свойствах навигации EF для универсальных типов. - person yopez83; 02.05.2018
comment
Но когда вы видите преобразованный SQL для этого LINQ, приведенные выше включения всегда показывают внутренние соединения. Есть ли способ, которым вы могли бы достичь левого или правого соединения, используя тот же метод включения. Для справки я включаю ссылку на свой пост stackoverflow.com/questions/55129505/ Спасибо. - person LOKI; 13.03.2019

Подсказка компилятору с использованием IQueryable<CampaignCreative> вместо var тоже сработает.

IQueryable<CampaignCreative> query = db.CampaignCreatives;
// or
DbQuery<CampaignCreative> query = db.CampaignCreatives;

При использовании var компилятор выводит DbSet<T> для query, что является более конкретным, чем тип, возвращаемый Include (то есть DbQuery<T> (= базовый класс DbSet<T>), реализующий IQueryable<T>), поэтому вы больше не можете присваивать результат переменной query. Отсюда и ошибка компилятора в строке query = query.Include(include).

person Slauma    schedule 03.04.2012

Я написал этот метод для динамического извлечения любого набора сущностей на основе их типов. Я использовал интерфейс IDbEntity, чтобы предоставить действительный ключ для поиска userId во всех классах. Метод Util.GetInverseProperties<T>() используется для получения свойств, необходимых в операторе Include.

public IEnumerable<T> GetItems<T>(string userId) where T : class, IDbEntity
{
    var query = db.Set<T>().Where(l => l.UserId==userId);

    var props = Util.GetInverseProperties<T>();
    foreach (var include in props)
        query = query.Include(include.Name);

    return query
        .AsNoTracking()
        .ToList();
}
public interface IDbEntity
{
    public string UserId { get; set; }
}
public static List<PropertyInfo> GetInverseProperties<T>()
{
    return typeof(T)
        .GetProperties()
        .Where(p => Attribute.IsDefined(p, typeof(InversePropertyAttribute)))
        .ToList();
}
person Nicolò Rebaioli    schedule 14.02.2020