1

Using Entity Framework 5, I have a generic method that retrieves entities from my context with optional parameters to filter, include related entities, and set the order of results. When I pass the method a set of include properties, however, it never modifies the query with joins to include the related entities. Any ideas why my query isn't updating?

Method:

public virtual IQueryable<TEntity> Get(
    Expression<Func<TEntity, bool>> filter = null,
    string includeProperties = "",
    Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null)
{        
    IQueryable<TEntity> query = dbSet.AsExpandable();

    if (filter != null)
    {
        query = query.Where(filter);
    }

    foreach (var includeProperty in includeProperties.Split
        (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
    {
        query = query.Include(includeProperty);
    }

    if (orderBy != null)
    {
        return orderBy(query);
    }
    else
    {
        return query;
    }
}

Query before foreach (var includeProperty... that remains unchanged afterwards

{SELECT 
[Extent1].[Pid] AS [Pid],     
[Extent1].[Created] AS [Created], 
[Extent1].[Creator] AS [Creator]
FROM [Administrator] AS [Extent1]}

EDIT...More Info

Originally I was using the following method call: AdministratorDTO admin = unitOfWork.AdministratorComponent.GetByID(userPid); on the following POCO class:

public virtual TEntity GetByID(object id)
{
    return dbSet.Find(id);
}

public class Administrator : IPrincipal
{
    [Key]
    [Required]
    [StringLength(1024)]
    [Display(Name = "PID")]
    public string Pid { get; set; }

    public DateTime Created { get; set; }

    public string Creator { get; set; }

    ...

    public ICollection<Role> Roles { get; set; }

    public ICollection<Area> Areas { get; set; }

    ...
}

And using AutoMapper to map to a DTO with the following configuration:

public class AdministratorDTO
{
    [Key]
    [Required]
    [StringLength(1024)]
    [Display(Name = "PID")]
    public string Pid { get; set; }

    ...

    public string[] Roles { get; set; }

    public string[] Areas { get; set; }
}


public class WebApiApplication : System.Web.HttpApplication
{
...
AutoMapperConfiguration.Configure();
}

public static class AutoMapperConfiguration
{
    public static void Configure()
    {
        ConfigureAdministratorMapping();
        ...
    }

    private static void ConfigureAdministratorMapping()
    {
        Mapper.CreateMap<Administrator, AdministratorDTO>()
            .ForMember(dest => dest.Roles, 
                opt => opt.MapFrom(src => src.Roles == null ? 
                    null : src.Roles.Select(r => r.Name).ToArray())) 
            .ForMember(dest => dest.Areas, 
                opt => opt.MapFrom(src => src.Areas == null ?
                    null : src.Areas.Select(a => a.Id).ToArray()));

        ...
    }
}

public class BusinessComponent<TEntity, TDto> 
    : IBusinessComponent<TEntity, TDto>
        where TEntity : class
        where TDto : class
{
    ...
    protected TDto Flatten(TEntity entity)
    {
        return Mapper.Map<TEntity, TDto>(entity);
    }
}

My understanding was that if I didn't mark the Administrator's navigation properties (Areas and Roles) as virtual they would be eagerly loaded, but I kept getting an empty string[] in my DTO.

I looked at the TEntity parameter going into my Flatten method and Areas/Roles were null before I called Map, so I don't think it was something to do with AutoMapper.

Next I tried using the following method call:

AdministratorDTO admin = unitOfWork.AdministratorComponent
    .Get(filter: a => a.Pid == "csherman", includeProperties: "Roles, Areas")
    .SingleOrDefault();

Finally, just in case the Include was being ignored because the navigation properties were not virtual, I added the virtual keyword to both Areas and Roles on my Administrator class. When I did this, both the GetByID and the Get(filter: ..., includeProperties: ...) methods worked, thereby including the Areas/Roles in my TEntity Flatten parameter and populating the string arrays in my DTO.

Problem solved I suppose, but...

Question is, especially for the GetById method, why did it work with the virtual keyword but not without?

If EF actually factors in the projection from the time of the original method call, why would these entities be included?

4

2 回答 2

2

如果您在包含后进行任何类型的投影,则包含不起作用。

这是一篇处理此问题以及如何解决此问题的帖子。

一个处理它的 SO question。

编辑:作为对您的编辑的回应,我试着环顾四周,看看是否能找到一个原因,为什么virtual关键字会让您Include工作但没有它就无法工作。我找不到任何可以直接回答这个问题的东西。

不过,这就是我认为正在发生的事情:virtual导航属性上的关键字告诉实体框架该属性应该使用延迟加载。如果有时您想急切地加载它们,那么您将使用Include. 我认为这Include就是构建的目的。我认为它试图专门寻找virtual具有该名称的属性,当它找不到它时,它会优雅地死去,无一例外。如果您不标记它,因为virtual我认为它的实现Include完全错过了它。我的猜测基于这样一个事实,即我所看到的所有文章都没有在Include延迟加载的上下文之外提到它——这意味着使用virtual关键字。

于 2013-02-28T16:53:27.597 回答
0

有一个严重的问题IQueryable.Include:底层对象必须是ObjectQueryor类型DbQuery,否则这个方法不起作用!

Include是泄漏的抽象,它仅适用于实体框架。EF 4.1 已经包含Include了泛型IQueryable,但它在内部仅将传递的泛型转换IQueryable为泛型ObjectQueryDbQuery并调用它们的Include.

https://stackoverflow.com/a/6791874/2444725

但是,当我向该方法传递一组包含属性时,它永远不会使用连接修改查询以包含相关实体。任何想法为什么我的查询没有更新?

您似乎正在使用 LinqKit:

IQueryable<TEntity> query = dbSet.AsExpandable();

扩展方法AsExpandable获取IQueryable参数并返回一个新类型的对象ExpandableQuery,用于装饰原始对象ObjectQueryDbQuery对象。扩展方法IQueryable.Include,应用于ExpandableQuery,无法进行转换,默默跳过。

为什么它与 virtual 关键字一起工作,但不是没有?

虚方法与包含无关。相反,它会打开延迟加载。这意味着导航属性不会在第一次请求时加载,而是在实际使用这些属性时在单独的请求中加载。

于 2015-06-21T13:20:34.303 回答