在LINQ to Entities中使用中间实体会导致System.NotSupportedException

时间:2015-06-23 15:28:50

标签: c# linq entity-framework-6

我的实体模型主要由6个实体组成,它们基于2个属性相互连接。

我必须根据旧的纯文本SQL查询构建182个LINQ查询。这些查询有一些共同点,所以为了避免重复,我构建了一个小框架来构建构建块之上的查询。我马上就会展示一些例子。

由于所有查询都是由6个实体的组合之间的连接构成的(从根实体SectionA开始),为方便起见,我构建了一个包装类JoinOfSections,它包装了所有这些部分

在该类上,我可以执行所有182个查询通用的简单LINQ评估。

例如

    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()方法是对常见模式的简化。由于结果集必须被展平以便旧组件处理它我发明了另一个适配器层

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

为了不复制和粘贴......

    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类表示数据集中的一行,它是多个实体之间JOIN的组合,如下所示。它的设计是为了让任何查询实例化其自定义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; }

}

基本上所有查询都要求对当前用户ID和可选检查日期过滤数据集,并且所有允许最大数量的结果。

我付出了尝试,用NotSupportedException概括了这个概念(我翻译的异常消息)

  

无法投射类型&#39; DiagnosticoSite.Data.Query.SezioneJoin&#39;成   &#39; DiagnosticoSite.Data.Query.SezioneJoin&#39 ;. LINQ to Entities支持   只有Enum或原始EDM数据类型

问题可能出在(a, d) => new SezioneJoin { A = a, D = d }行:如果我选择匿名类型,LINQ查询可以完美地运行,但是我无法将查询对象传递给装饰的受保护方法它有额外的常见检查。

作为他们的182查询,我必须找到一种方法来为所有复制和粘贴的查询添加常用检查。

我想知道如何使用&#34;缓冲区&#34;来操作LINQ to Entities查询。或&#34;中间&#34;不在数据上下文中的实体,以便复杂的查询本身可以作为参数传递给装饰器方法。

ToString()返回的IQueryable的枚举和调用Run方法上都会发生错误。我需要ToString来提取发给DB的查询

1 个答案:

答案 0 :(得分:1)

EF可能不知道SezioneJoin是什么。

您可以将这样的类型定义为实体,也许它可以工作。但是,如果您的CommonFiltering是现实的,则没有必要。考虑:

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

实际上,如果过滤点同时关注AD的某些质量,您只需要担心将连接的结果而不是组件传递给它步。在这里,您的过滤仅关注A,这是一个已定义的类型。

如果你确实需要处理类似的事情,你可以建立表达式。

为了简化,我们只考虑一个带有签名IQueryable<T> Filter<T>(IQueryable<T> source, bool makeA21Match)的版本。如果makeA21Match为真,那么我们会在查询中添加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;
}

这比在C#代码中使用lambda表达式更复杂,并且缺少类型的编译时检查,但它确实意味着我们可以将表达式应用于匿名类型的可查询,并且性能应该是可比较的在这种情况下。

平衡的方法是首先尽可能地过滤sezione_asezione_d,因为这可以更容易地完成,然后有一个方法只处理更复杂的手工编码为那些绝对需要它的案件建立表达式。如有必要,您甚至可以缓存生成的Expression<Func<T, bool>>

相关问题