[英]Using an intermediate entity in LINQ to Entities leads to System.NotSupportedException
My entity model is made primarily of 6 entities that join among themselves based on 2 attributes. 我的实体模型主要由6个实体组成,它们基于2个属性相互连接。
I have to build 182 LINQ queries based on old plaintext SQL queries. 我必须根据旧的纯文本SQL查询构建182个LINQ查询。 Those queries have some parts in common, so that in order to avoid repeats I have built a little framework to build the queries on top of building blocks. 这些查询有一些共同点,所以为了避免重复,我构建了一个小框架来构建构建块之上的查询。 I will show examples right away. 我马上就会展示一些例子。
Since all queries are made of a join between a combination of the 6 entities (starting from root entity SectionA
) I have built, for my convenience, a wrapper class JoinOfSections
that wraps all those sections. 由于所有查询都是由6个实体的组合之间的连接构成的(从根实体SectionA
),为了方便起见,我构建了一个包装器类JoinOfSections
,它包装了所有这些部分。
On that class I can perform simple LINQ evaluations that are common to all 182 queries. 在该类上,我可以执行所有182个查询通用的简单LINQ评估。
For example 例如
public override IQueryable<QueryRow> Run(Models.auitool2014Entities dataContext, int aUserId, DateTime? dataInizioControllo, DateTime? dataFineControllo, string[] a52Exclude, bool codifiche2014)
string now = DateTime.Now.ToString("yyyyMMdd");
var sj = dataContext.sezione_a.SelectMany(
a => dataContext.sezione_d.Where(d => a.A03 == d.A03 && a.utente == d.utente).DefaultIfEmpty(),
(a, d) => new SezioneJoin { A = a, D = d });
sj = CommonFiltering(sj, aUserId, dataInizioControllo, dataFineControllo, a52Exclude);
return (from SezioneJoin ssj in sj
let a = ssj.A
let d = ssj.D
where
a.utente == aUserId &&
(
String.IsNullOrEmpty(a.A21) || String.IsNullOrEmpty(a.A51) ||
a.A51.CompareTo(a.A21) < 0 ||
a.A21.CompareTo(now) > 0 ||
a.A51.CompareTo(now) > 0 ||
a.A21.CompareTo("19000101") < 0 ||
a.A51.CompareTo("19000101") < 0
)
select ssj).Select(Select());
}
protected virtual IQueryable<SezioneJoin> CommonFiltering(IQueryable<SezioneJoin> sj, int aUserId, DateTime? dataInizioControllo, DateTime? dataFineControllo, string[] a52Exclude)
{
sj = sj.Where(x => x.A.utente == aUserId);
if (dataInizioControllo != null)
{
string dataInzio = dataInizioControllo.Value.ToString("yyyyMMdd");
sj = sj.Where(x => x.A.A21.CompareTo(dataInzio) >= 0);
}
if (dataFineControllo != null)
{
string dataFine = dataFineControllo.Value.ToString("yyyyMMdd");
sj = sj.Where(x => x.A.A21.CompareTo(dataFine) <= 0);
}
if (a52Exclude != null)
sj = sj.Where(x => !a52Exclude.Contains(x.A.A52));
return sj.Take(Parameters.ROW_LIMIT);
}
Select()
method is a simplification of a common pattern. Select()
方法是常见模式的简化。 Since the result set must be flattened in order for older components to process it I have invented another adapter layer 由于结果集必须展平以便旧组件处理它,我发明了另一个适配器层
[Serializable]
public class QueryRow
{
public string A01 { get; set; }
public string A01a { get; set; }
public string A01b { get; set; }
public string A02 { get; set; }
public string A03 { get; set; }
public string A11 { get; set; }
public string A12 { get; set; }
// Dozens of string members
}
In order not to copy&paste... 为了不复制和粘贴......
protected virtual Expression<Func<SezioneJoin, QueryRow>> Select()
{
return sj => new QueryRow
{
A01 = sj.A.A01,
A01a = sj.A.A01a,
A01b = sj.A.A01b,
A02 = sj.A.A02,
A03 = sj.A.A03,
A11 = sj.A.A11,
A12 = sj.A.A12,
A12a = sj.A.A12a,
A12b = sj.A.A12b,
A12c = sj.A.A12c,
A21 = sj.A.A21,
A22 = sj.A.A22,
A23 = sj.A.A23,
A24 = sj.A.A24,
A25 = sj.A.A25,
A31 = sj.A.A31,
A31a = sj.A.A31a,
A31b = sj.A.A31b,
A32 = sj.A.A32,
}
SezioneJoin
class represents a row in the dataset that is a combination of JOIN
s between a number of entities, is as follows. SezioneJoin
类表示数据集中的一行,它是多个实体之间JOIN
的组合,如下所示。 It is designed in order for any query to instantiate its custom JOIN (eg A inner D, A left D left E, A inner D left H) 它的设计是为了让任何查询实例化其自定义JOIN(例如A内部D,A左D左E,A内D左H)
public class SezioneJoin
{
public SezioneA A { get; set; }
public SezioneD D { get; set; }
public SezioneE E { get; set; }
public SezioneF F { get; set; }
public SezioneG G { get; set; }
public SezioneH H { get; set; }
}
Basically all queries require that the dataset is filtered on the current user ID and optional check dates, plus they all allow a maximum number of results. 基本上所有查询都要求在当前用户ID和可选检查日期上过滤数据集,并且它们都允许最大数量的结果。
I paid my attempt to generalize the concept with NotSupportedException
(exception message translated by me) 我付出了尝试用NotSupportedException
概括概念(由我翻译的异常消息)
Unable to cast type 'DiagnosticoSite.Data.Query.SezioneJoin' into 'DiagnosticoSite.Data.Query.SezioneJoin'. 无法将“DiagnosticoSite.Data.Query.SezioneJoin”类型转换为“DiagnosticoSite.Data.Query.SezioneJoin”。 LINQ to Entities supports only Enum or primitive EDM data types LINQ to Entities仅支持Enum或原始EDM数据类型
The problem may lie in the (a, d) => new SezioneJoin { A = a, D = d }
line: if I select an anonymous type the LINQ query works just flawlessly, but then I cannot pass the query object to the protected method that decorates it with additional common checks. 问题可能在于(a, d) => new SezioneJoin { A = a, D = d }
行:如果我选择一个匿名类型LINQ查询工作正常,但是我无法将查询对象传递给受保护的用额外的常见检查来装饰它的方法。
Being them 182 queries it is important for me to find a way to add common checks to all queries that is not copy&paste. 作为182查询,我必须找到一种方法来为所有不复制和粘贴的查询添加常用检查。
I would like to know how I can manipulate a LINQ to Entities query using a "buffer" or "intermediate" entity that is not in the data context so that the query itself, being complex, may be passed as a parameter to a decorator method. 我想知道如何使用不在数据上下文中的“缓冲区”或“中间”实体来操纵LINQ to Entities查询,以便查询本身(复杂)可以作为参数传递给装饰器方法。
The error occurs both on enumerating and on calling ToString()
method of the IQueryable returned by Run
. 在枚举和调用Run
返回的IQueryable的ToString()
方法时都会发生错误。 I needed the ToString to extract the query issued to DB 我需要ToString来提取发给DB的查询
EF presumably doesn't know what a SezioneJoin
is. EF可能不知道SezioneJoin
是什么。
You could define such a types as an entity, and maybe it would work. 您可以将这样的类型定义为实体,也许它可以工作。 However, if your CommonFiltering
is realistic, that wouldn't be necessary. 但是,如果您的CommonFiltering
是现实的,那就没有必要了。 Consider: 考虑:
var q = CommonFiltering(dataContext.sezione_a, aUserId, dataInizioControllo, dataFineControllo, a52Exclude)
.Join(
dataContext.sezione_d,
a => new { A03 = a.A03, User = a.utente },
d => new { A03 = d.A03, User = d.utente },
(a, d) => new SezioneJoin { A = a, D = d }
)
.Where(x =>
...
).Take(Parameters.ROW_LIMIT);
protected virtual IQueryable<AType> CommonFiltering(IQueryable<SezioneA> sj, int aUserId, DateTime? dataInizioControllo, DateTime? dataFineControllo, string[] a52Exclude)
{
sj = sj.Where(x => x.utente == aUserId);
if (dataInizioControllo != null)
{
string dataInzio = dataInizioControllo.Value.ToString("yyyyMMdd");
sj = sj.Where(x => x.A21.CompareTo(dataInzio) >= 0);
}
if (dataFineControllo != null)
{
string dataFine = dataFineControllo.Value.ToString("yyyyMMdd");
sj = sj.Where(x => x.A21.CompareTo(dataFine) <= 0);
}
if (a52Exclude != null)
sj = sj.Where(x => !a52Exclude.Contains(x.A52));
return sj;
}
Really, you only need to worry about passing the result of the join rather than the components to it, if a point of the filtering cares about some quality of both A
and D
at the same step. 实际上,如果过滤点在同一步骤中关注A
和D
某些质量,您只需要担心将连接的结果而不是组件传递给它。 Here your filtering cares only about A
and that's a defined type. 在这里,您的过滤只关心A
,这是一个定义的类型。
If you really need to deal with something like that though, you could build up the expression. 如果你真的需要处理类似的东西,你可以建立表达式。
To simplify let's just consider a version with the signature IQueryable<T> Filter<T>(IQueryable<T> source, bool makeA21Match)
. 为了简化,我们只考虑具有签名IQueryable<T> Filter<T>(IQueryable<T> source, bool makeA21Match)
。 If makeA21Match
is true then we add Where(sj => sj.A.A21 == sj.D.A21)
to the query, and otherwise we don't: 如果makeA21Match
为true,那么我们将Where(sj => sj.A.A21 == sj.D.A21)
到查询中,否则我们不会:
private static IQueryable<T> Filter<T>(IQueryable<T> source, bool makeA21Match)
{
if(makeA21Match)
{
var getA = typeof(T).GetProperty("A"); // .A
var getD = typeof(T).GetProperty("D"); // .D
var getAA21 = typeof(SezioneA).GetProperty("A21"); // a.A21 for some A.
var getDA21 = typeof(SezioneD).GetProperty("A21"); // d.A21 for some D.
var parExp = Expression.Parameter(typeof(T)); // sj.
var getAExp = Expression.Property(parExp, getA); // sj.A
var getDExp = Expression.Property(parExp, getD); // sj.D
var getAA21Exp = Expression.Property(getAExp, getAA21); // sj.A.A21
var getDA21Exp = Expression.Property(getDExp, getDA21); // sj.D.A21
var eqExp = Expression.Equal(getAA21Exp, getDA21Exp); // sj.A.A21 == sj.D.A21
var λExp = Expression.Lambda<Func<T, bool>>(eqExp, parExp); // sj => sj.A.A21 == sj.D.A21
source = source.Where(λExp);
}
return source;
}
This is a lot more convoluted than just having a lambda expression in the C# code, and lacks compile-time checking of types, but it does mean we can apply the expression to a queryable of anonymous type, and performance should be comparable in such a case. 这比在C#代码中使用lambda表达式更复杂,并且缺少类型的编译时检查,但它确实意味着我们可以将表达式应用于匿名类型的可查询,并且性能应该在这样的案件。
A balanced approach would be to first filter sezione_a
and sezione_d
as much as possible, since that can be done more easily, and then have a method that only deals with the more complicated hand-coding of expression-building for those cases that absolutely need it. 一个平衡的方法是首先尽可能地过滤sezione_a
和sezione_d
,因为这可以更容易地完成,然后有一个方法只处理那些绝对需要它的情况下更复杂的表达式构建手工编码。 You can even cache the Expression<Func<T, bool>>
produced if necessary. 您甚至可以缓存必要时生成的Expression<Func<T, bool>>
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.