实体框架-有没有自动方法而无需Include()来自动加载子实体的方法吗?


77

有没有办法装饰您的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类或在我的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()多个子实体


内森,非常有趣的挑战。请说明为什么/希望避免使用诸如include()之类的主动加载技术。
Dave Alperovich

不,您不能,但是没有什么可以阻止您在DbContext或存储库类中创建类似DbQuery<Car> CompleteCars { get { return ...(您的长Include链)的属性。如果可以的话,这不会切断从数据库中获取“裸车”的可能性吗?
Gert Arnold

我想避免这种情况,因为我有一个复杂的模型,但是记录很少,所以我希望数据能尽快加载,但又不想错过应用程序中任何位置的孙子项。
内森·杰弗斯

Answers:


66

不,您不能在映射中做到这一点。典型的解决方法是简单的扩展方法:

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在您正在使用的核心实体上工作-该实体定义了查询的形状,因此调用后您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)因为当您请求紧急加载其嵌套实体时应自动将其添加。

提防此类复杂的热切加载结构产生的复杂查询。这可能会导致非常差的性能以及从数据库传输的大量重复数据


那正是我所需要的!谢谢!
Nathan Geffers 2013年

我可以为子实体嵌套对这些扩展方法的调用吗?(请参阅更新的问题)
Nathan Geffers 2013年

1
具有[包含]属性可以赢得我的投票!(请参阅答案中的链接)
Nathan Geffers 2013年

因此,这意味着当我需要装载公共汽车,汽车,卡车等时,我仍然需要编写一种有关装载轮辋和轮胎的单独方法。我只将它们包括在我的LoadBus,LoadTruck,LoadCar方法中,但是我试图避免有这些方法需要了解有关Tire的孩子的任何知识。太糟糕了,但至少我有我的答案。谢谢!
内森·杰弗斯

这对性能有好处,lazy loading或者eager loading我可以从这里看到它lazy loading,实际上我正在尝试搜索与其他表有关系的产品,您认为呢?
Shaiju T

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));
    }

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;
    }

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);
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.