【发布时间】:2016-01-12 14:50:15
【问题描述】:
TL;DR
我想在 LINQ to Entities 中编写一个查询并告诉它我永远不会加载实体的子实体。我如何做到这一点没有投影?
例如,
return (from a in this.Db.Assets
join at in this.Db.AssetTypes on a.AssetTypeId equals at.AssetTypeId
join ast in this.Db.AssetStatuses on a.AssetStatusId equals ast.AssetStatusId
select new {
a = a,
typeDesc = at.AssetTypeDesc,
statusDesc = ast.AssetStatusDesc
}).ToList().Select(anon => new AssetViewModel(anon.a, anon.typeDesc, anon.statusDesc)).ToList();
我希望在我定义的匿名类型上将名为 Asset 的实体拉入 a,并且当我调用 ToList() 时,我不希望资产的子项 Status 和 Type 延迟加载.
编辑:经过一些随机的 Visual Studio autcomplete 调查,大部分工作可以通过关闭 DbContext 中的延迟加载来完成:
this.Db.Configuration.LazyLoadingEnabled = false;
不幸的是,如果您处理查询结果的工作确实有一些子表,即使关闭了LazyLoadingEnabled,对于其中的某些子集,事情可能仍然“有效”iff这些子表的数据在DbContext 中早先已经加载了children——也就是说,如果这些children 已经缓存了他们的上下文——这可能会产生一些令人惊讶且暂时令人困惑的结果。
也就是说,我想在查询时显式加载一些个子实体,并完全切断与其他子实体的任何关系。
最好是主动加载某些实体并忽略其余实体的某种方式。也就是说,我可以调用 ToList() 而不必担心会丢失大量的数据库连接。
上下文
我有一个案例,我正在使用来自名为 Asset 的实体的 LINQ to Entities 查询的结果对视图模型进行水合。 Asset 表有几个子表,Type 和 Status。类型和状态都有描述字段,我的视图模型包含两个描述。让我们假设它和这个查询一样复杂。
因此,我想在一个数据库查询中从连接到类型和状态的资产表中提取所有内容,在此期间我提取类型和状态描述。换句话说,我不想延迟加载该信息。
WET(Woeful Entity reTranscription?)
我们现在正在做的,从连接的角度来看,这正是我想要的,是通常的 .Select 进入视图模型,具有繁琐的字段匹配。
return (from a in this.Db.Assets
join at in this.Db.AssetTypes on a.AssetTypeId equals at.AssetTypeId
join ast in this.Db.AssetStatuses on a.AssetStatusId equals ast.AssetStatusId
select new AssetViewModel
{
AssetId = a.AssetId,
// *** LOTS of fields from Asset removed ***
AssetStatusDesc = ast.AssetStatusDesc,
AssetTypeDesc = at.AssetTypeDesc
}).ToList();
这很好,因为永远不会访问 Asset 的 Status 和 Type 子实体,并且 没有延迟加载。 SQL 是针对所有资产的一个数据库命中的一键连接。完美。
担心是// *** LOTS of fields from Asset removed *** 中反复出现的所有问题。目前,我们在每个怪异的查询中都有这个投影,这显然不是 DRY。而且这意味着当 Asset 表发生变化时,新字段很少会包含在每个投影中(因为是人为的),这很糟糕。
顺便说一句,我没有看到快速解决查询的方法。如果我想在单个查询中完成,我必须有连接。我可以在不同的方法中添加 wheres ,但我不确定每次如何跳过投影。或者我可以在级联方法中向查询添加连接,但是我的投影仍然是“存储库绑定”,如果我在其他地方使用这些类型的查询,这不是最好的情况。但我敢打赌我在这里犯了一些错误。
哑巴
当我尝试从资产向我的视图模型添加强制转换并更改为类似的东西时,从代码的角度来看这很漂亮,尽管我被延迟加载所困扰——两个额外的数据库命中每个资产,一个用于状态,一个用于类型。
return (from a in this.Db.Assets
select a).ToList().Select(asset => (AssetViewModel)asset).ToList();
正如我们所期望的那样,因为我使用的是...之类的行
AssetTypeDesc = a.AssetType.AssetTypeDesc,
... 在强制转换代码中。所以这很愚蠢。简洁,可重复使用,但很愚蠢。这就是为什么我们讨厌那些不检查 SQL 就使用 ORM 的人。 ;^)
太聪明了,有点儿
但是后来我尝试变得太聪明了,为视图模型使用了一个新的构造函数,该构造函数将资产实体和两个描述值作为strings,最终导致了相同的延迟加载问题(因为,呃,在选择匿名对象之前首先ToList() 意味着我们不知道资产将如何被使用,并且我们被困在撤回所有内容以确保安全(我假设)。
//Use anon type to skirt "Only parameterless constructors
//and initializers are supported in LINQ to Entities,"
//issue.
return (from a in this.Db.Assets
join at in this.Db.AssetTypes on a.AssetTypeId equals at.AssetTypeId
join ast in this.Db.AssetStatuses on a.AssetStatusId equals ast.AssetStatusId
select new {
a = a,
typeDesc = at.AssetTypeDesc,
statusDesc = ast.AssetStatusDesc
}).ToList().Select(anon => new AssetViewModel(anon.a, anon.typeDesc, anon.statusDesc)).ToList();
如果只有某种方式可以说,“将这些匿名对象转换为
List,但在执行此操作时不要延迟加载资源的子对象。”
我读过一些关于DataLoadOptions.LoadWith()、which probably provides an okay solution 的文章,我可能最终会这样做,但这并不是我要问的。我认为这是一个全球性的设置(?我认为只是为了数据上下文的生命,应该是单个控制器交互),我可能不一定想要设置。我可能也想要ObjectTrackingEnabled = false,但我还没想好。
我也不想使用自动映射器。
【问题讨论】:
-
@mcse3010 我将进行编辑以更清楚地提出我在下面提到的问题。即使
LazyLoadingEnabled是false,似乎也存在已缓存信息弹出的问题。也就是说,即使在某些前卫但仍然足够常见的情况下关闭延迟加载,实体仍然可以加载。我希望它们严格地“从不加载”。
标签: c# entity-framework linq-to-entities