79

有没有办法装饰你的 POCO 类来自动加载子实体,而不必Include()每次加载它们时都使用它们?

假设我有一辆汽车,具有轮子、门、发动机、保险杠、窗户、排气等复杂类型的属性。在我的应用程序中,我需要从我的 DbContext 20 个不同的地方加载我的汽车,并使用不同的查询等。我不想每次我想装载我的汽车时都指定我想包含所有属性。

我想说

List<Car> cars = db.Car
                .Where(x => c.Make == "Ford").ToList();

//NOT .Include(x => x.Wheels).Include(x => x.Doors).Include(x => x.Engine).Include(x => x.Bumper).Include(x => x.Windows)


foreach(Car car in cars)
{

//I don't want a null reference here.

String myString = car.**Bumper**.Title;
}

我可以以某种方式装饰我的 POCO 类或在我的OnModelCreating()或在 EF 中设置一个配置,告诉它在我装载汽车时只装载我汽车的所有部件吗?我很想这样做,所以我的理解是让我的导航属性虚拟化已经过时了。我知道 NHibernate 支持类似的功能。

只是想知道我是否遗漏了什么。提前致谢!

干杯,

弥敦道

我喜欢下面的解决方案,但想知道是否可以嵌套对扩展方法的调用。例如,假设我对 Engine 有类似的情况,它有很多我不想在任何地方都包含的部分。我可以做这样的事情吗?(我还没有找到一种可行的方法)。这样,如果以后我发现Engine需要FuelInjectors,我可以只在BuildEngine中添加它,而不必在BuildCar中也添加它。另外,如果我可以嵌套调用,如何嵌套调用集合?想从我的 BuildCar() 中为我的每个轮子调用 BuildWheel()?

public static IQueryable<Car> BuildCar(this IQueryable<Car> query) {
     return query.Include(x => x.Wheels).BuildWheel()
                 .Include(x => x.Doors)
                 .Include(x => x.Engine).BuildEngine()
                 .Include(x => x.Bumper)
                 .Include(x => x.Windows);
}

public static IQueryable<Engine> BuildEngine(this IQueryable<Engine> query) {
     return query.Include(x => x.Pistons)
                 .Include(x => x.Cylendars);
}

//Or to handle a collection e.g.
 public static IQueryable<Wheel> BuildWheel(this IQueryable<Wheel> query) {
     return query.Include(x => x.Rim)
                 .Include(x => x.Tire);
}

这是另一个非常相似的线程,以防在这种情况下对其他人有帮助,但它仍然无法处理对扩展方法的下一个调用。

实体框架 linq 查询 Include() 多个子实体

4

4 回答 4

67

不,您不能在 mapping 中这样做。典型的解决方法是简单的扩展方法:

public static IQueryable<Car> BuildCar(this IQueryable<Car> query) {
     return query.Include(x => x.Wheels)
                 .Include(x => x.Doors)
                 .Include(x => x.Engine)
                 .Include(x => x.Bumper)
                 .Include(x => x.Windows);
}

现在,每次您想查询Car所有关系时,您只需执行以下操作:

var query = from car in db.Cars.BuildCar()
            where car.Make == "Ford"
            select car;

编辑:

您不能以这种方式嵌套调用。包含适用于您正在使用的核心实体 - 该实体定义了查询的形状,因此在您调用后您Include(x => Wheels)仍在使用IQueryable<Car>并且您不能调用扩展方法IQueryable<Engine>。您必须再次开始Car

public static IQueryable<Car> BuildCarWheels(this IQuerable<Car> query) {
    // This also answers how to eager load nested collections 
    // Btw. only Select is supported - you cannot use Where, OrderBy or anything else
    return query.Include(x => x.Wheels.Select(y => y.Rim))
                .Include(x => x.Wheels.Select(y => y.Tire));
}

你会以这种方式使用该方法:

public static IQueryable<Car> BuildCar(this IQueryable<Car> query) {
     return query.BuildCarWheels()
                 .Include(x => x.Doors)
                 .Include(x => x.Engine)
                 .Include(x => x.Bumper)
                 .Include(x => x.Windows);
}

该用法不会调用Include(x => x.Wheels),因为它应该在您请求急切加载其嵌套实体时自动添加。

当心由这种复杂的急切加载结构产生的复杂查询。这可能会导致性能非常差,并从数据库中传输大量重复数据

于 2013-01-25T11:31:16.757 回答
8

遇到了同样的问题,并看到了另一个提到Include属性的链接。我的解决方案假定您创建了一个名为IncludeAttribute. 使用以下扩展方法和实用方法

    public static IQueryable<T> LoadRelated<T>(this IQueryable<T> originalQuery)
    {
        Func<IQueryable<T>, IQueryable<T>> includeFunc = f => f;
        foreach (var prop in typeof(T).GetProperties()
            .Where(p => Attribute.IsDefined(p, typeof(IncludeAttribute))))
        {
            Func<IQueryable<T>, IQueryable<T>> chainedIncludeFunc = f => f.Include(prop.Name);
            includeFunc = Compose(includeFunc, chainedIncludeFunc);
        }
        return includeFunc(originalQuery);
    }

    private static Func<T, T> Compose<T>(Func<T, T> innerFunc, Func<T, T> outerFunc)
    {
        return arg => outerFunc(innerFunc(arg));
    }
于 2017-09-18T17:31:43.650 回答
0

如果您真的需要包含所有导航属性(您可能会遇到性能问题),您可以使用这段代码。
如果您还需要急切地加载集合(即使是最糟糕的想法),您可以删除代码中的 continue 语句。
上下文是您的上下文(如果您在上下文中插入此代码)

    public IQueryable<T> IncludeAllNavigationProperties<T>(IQueryable<T> queryable)
    {
        if (queryable == null)
            throw new ArgumentNullException("queryable");

        ObjectContext objectContext = ((IObjectContextAdapter)Context).ObjectContext;
        var metadataWorkspace = ((EntityConnection)objectContext.Connection).GetMetadataWorkspace();

        EntitySetMapping[] entitySetMappingCollection = metadataWorkspace.GetItems<EntityContainerMapping>(DataSpace.CSSpace).Single().EntitySetMappings.ToArray();

        var entitySetMappings = entitySetMappingCollection.First(o => o.EntityTypeMappings.Select(e => e.EntityType.Name).Contains(typeof(T).Name));

        var entityTypeMapping = entitySetMappings.EntityTypeMappings[0];

        foreach (var navigationProperty in entityTypeMapping.EntityType.NavigationProperties)
        {
            PropertyInfo propertyInfo = typeof(T).GetProperty(navigationProperty.Name);
            if (propertyInfo == null)
                throw new InvalidOperationException("propertyInfo == null");
            if (typeof(System.Collections.IEnumerable).IsAssignableFrom(propertyInfo.PropertyType))
                continue;

            queryable = queryable.Include(navigationProperty.Name);
        }

        return queryable;
    }
于 2019-11-19T18:33:38.527 回答
0

进一步研究 Lunyx 的答案,我添加了一个重载以使其与字符串集合一起使用(其中字符串是属性名称,例如 "propertyA" 或 "propertyA.propertyOfAA" ):

  public static IQueryable<T> LoadRelated<T>(this IQueryable<T> originalQuery, ICollection<string> includes)
    {
        if (includes == null || !includes.Any()) return originalQuery;

        Func<IQueryable<T>, IQueryable<T>> includeFunc = f => f;
        foreach (var include in includes)
        {
            IQueryable<T> ChainedFunc(IQueryable<T> f) => f.Include(include);
            includeFunc = Compose(includeFunc, ChainedFunc);
        }

        return includeFunc(originalQuery);
    }
于 2020-07-08T12:12:32.913 回答