实体框架-有没有一种方法可以自动加载子实体,而不包括() ?

有没有一种方法可以装饰您的 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 需要 FuelInjectors,我可以只在 BuildEngine 中添加它,而不必在 BuildCar 中也添加它。另外,如果我可以嵌套调用,那么如何嵌套对集合的调用?喜欢在 BuildCar ()中为每个轮子调用 BuildWheal()吗?

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 ()多个子实体

46920 次浏览

典型的解决方法是简单的扩展方法:

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),因为当您请求立即加载其嵌套实体时,应该自动添加 Include(x => x.Wheels)

小心这种复杂的急切加载结构产生的复杂查询。它可能导致非常差的性能和来自数据库的 传输了大量的重复数据

遇到了同样的问题,看到了另一个提到 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));
}

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

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

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

自 EFCore6以来,有一个自动包含的方法来配置是否应该自动包含导航。

这可以在 DbContext 类的 OnModelCreate 方法中完成:

modelBuilder.Entity<Car>().Navigation(e => e.Wheels).AutoInclude();

这将在运行以下查询时为每个 Car加载 Weels

var cars = dbContext.Cars.ToList();

参见 自动包含导航的模型配置