【问题标题】:Entity Framework Casting Error实体框架转换错误
【发布时间】:2011-07-04 11:04:39
【问题描述】:

以下工作完美:

IQueryable<Property> PropertyQuery = PropertyDAO.SearchWithAdditionalParameters(/* elided */);
IQueryable<long> propertyIdQuery = PropertyQuery.Select(p => p.PropertyId);

var relevantFMVs = PropertyDAO.db.FMVHistories.Where(f => propertyIdQuery.Contains(f.PropertyId)).ToList();

但是下面的事情发生了:

IQueryable<Property> PropertyQuery = PropertyDAO.SearchWithAdditionalParameters(/* elided */);

var relevantFMVs = PropertyDAO.db.FMVHistories.Where(f => PropertyQuery.Select(p => p.PropertyId).Contains(f.PropertyId)).ToList();

(请注意,我没有单独创建 propertyIdQuery,而是将查询本身替换为变量所在的位置)

例外是

无法转换类型 'System.Linq.IQueryable1' to type 'System.Linq.IQueryable1'。 LINQ 到 实体仅支持投射实体 数据模型基元类型。

有人可以解释一下 EF (4) 在幕后做了什么,只使第一个查询起作用,即使它们表面上是等价的?

我知道IQueryable&lt;T&gt; 和表达式树在幕后做了很多事情,但是将中间步骤保存到局部变量中会如何影响结果呢?

编辑

根据要求,这里是被调用的完整方法,以及该方法调用的方法:

    public IQueryable<Property> BasicSearchFromConstraints(PropertyInvoiceConstraints constraints) {
        return ExecuteSearchFromConstraints((dynamic)constraints.PropertyInst, constraints.CompanyNumber, constraints.TaxSubType, constraints.PhaseID, constraints.State, constraints.County, constraints.City, constraints.Jurisdiction);
    }

    private IQueryable<T> ExecuteSearchFromConstraints<T>(T property, int CompanyNumber, byte SubType, byte PhaseID, string State, string County, string City, string Jurisdiction) where T : Property {
        IQueryable<T> result = base.db.Properties.OfType<T>();

        if (SubType > 0)
            result = result.Where(p => p.TaxSubTypeId == SubType);
        if (CompanyNumber > 0)
            result = result.Where(p => p.CompanyNum == CompanyNumber);
        if (!String.IsNullOrEmpty(State))
            result = result.Where(p => p.State == State);
        if (!String.IsNullOrEmpty(County))
            result = result.Where(p => p.County == County);
        if (!String.IsNullOrEmpty(City))
            result = result.Where(p => p.City == City);
        if (!String.IsNullOrEmpty(Jurisdiction))
            result = result.Where(p => p.Jurisdiction == Jurisdiction);

        if (PhaseID > 0)
            result = result.Where(p => p.PhaseId == PhaseID);

        return result;
    }

    public virtual IQueryable<Property> SearchWithAdditionalParameters(DataLayer.DAO.PropertyInvoiceConstraints constraints, string propertyNumber = "", string altDesc = "", string countyAcctNumber = "", string City = "", string Jurisdiction = "", string secondaryStateID = "", string LegalDesc = "", string status = "", int? TaxYear = null) {
        IQueryable<Property> result = BasicSearchFromConstraints(constraints);

        if (!String.IsNullOrEmpty(status))
            result = result.Where(p => p.Status == status);

        if (!String.IsNullOrEmpty(propertyNumber))
            result = result.Where(p => p.PropertyNum.Contains(propertyNumber));

        if (!String.IsNullOrEmpty(altDesc))
            result = result.Where(p => p.AltDescription.Contains(altDesc));

        if (!String.IsNullOrEmpty(countyAcctNumber))
            result = result.Where(p => p.CountyAccountNum.Contains(countyAcctNumber));

        if (!String.IsNullOrEmpty(City))
            result = result.Where(p => p.City.Contains(City));

        if (!String.IsNullOrEmpty(Jurisdiction))
            result = result.Where(p => p.Jurisdiction.Contains(Jurisdiction));

        if (TaxYear.HasValue)
            result = result.Where(p => p.FMVHistories.Any(f => f.TaxYear == TaxYear));

        if (constraints.FMVPhaseID > 0)
            result = result.Where(p => p.FMVHistories.Any(f => f.PhaseId == constraints.FMVPhaseID));

        if (!String.IsNullOrEmpty(secondaryStateID))
            if (constraints.PropertyInst is WellDetail)
                result = result.OfType<WellDetail>().Where(w => w.SecondaryStateId == secondaryStateID);
            else
                throw new ApplicationException("Invalid use -> Secondary State ID can only be set when searching for Well property types");

        if (!String.IsNullOrEmpty(LegalDesc))
            if (constraints.PropertyInst is RealEstateDetail)
                result = result.OfType<RealEstateDetail>().Where(r => r.LegalDescr.Contains(LegalDesc));
            else if (constraints.PropertyInst is RealEstateServicingDetail)
                result = result.OfType<RealEstateServicingDetail>().Where(r => r.LegalDescr.Contains(LegalDesc));
            else throw new ApplicationException("Invalid use -> Legal Description can only be set when searching for either real estate or real estate servicing property types");

        return result;
    }

编辑

我真的希望 Akash 的答案是正确的,但如果是,我希望这里的中间查询会爆炸,但实际上这三个都可以正常工作。

我开始怀疑我在类型 Property(来自原始示例)上的继承结构可能与此有关。

        DummyBookModelEntities db = new DummyBookModelEntities();

        IQueryable<int> BookIds = db.Books.Where(b => b.id < 4).Select(b => b.id);
        IQueryable<Book> BooksFromIdQuery = db.Books.Where(b => b.id < 4);

        try {
            var l1 = db.Books.Where(b => BookIds.Contains(b.id)).ToList();
            Console.WriteLine("ID Query With ID Local Var Worked: count = {0}", l1.Count);
        } catch (Exception E) {
            Console.WriteLine("ID Query Failed:");
            Console.WriteLine(E.ToString());
            Console.WriteLine();
        }

        try {
            var l1 = db.Books.Where(b => BooksFromIdQuery.Select(b_inner => b_inner.id).Contains(b.id)).ToList();
            Console.WriteLine("ID Query With Whole Book Local Var Worked: count = {0}", l1.Count);
        } catch (Exception E) {
            Console.WriteLine("ID Query With Whole Book Local Var Failed:");
            Console.WriteLine(E.ToString());
            Console.WriteLine();
        }

        try {
            var l1 = db.Books.Where(b => BooksFromIdQuery.Contains(b)).ToList();
            Console.WriteLine("Whole Book sub query without select worked: count = {0}", l1.Count);
        } catch (Exception E) {
            Console.WriteLine("Whole Book sub query without select:");
            Console.WriteLine(E.ToString());
            Console.WriteLine();
        }

编辑

我添加了一些继承,现在底部两个查询失败。看起来任何时候您在查询中有OfType(),EF 根本不想解析查询中的整个查询;您必须将子步骤分解为局部变量。

今晚我会奖励 Akash 赏金,除非有人要添加其他内容。

        DummyBookModelEntities db = new DummyBookModelEntities();

        IQueryable<int> BookIds = db.Books.OfType<SciFiBook>().Where(b => b.id < 4).Select(b => b.id);
        IQueryable<Book> BooksFromIdQuery = db.Books.OfType<SciFiBook>().Where(b => b.id < 4);

        try {
            var l1 = db.Books.Where(b => BookIds.Contains(b.id)).ToList();
            Console.WriteLine("ID Query With ID Local Var Worked: count = {0}", l1.Count);
        } catch (Exception E) {
            Console.WriteLine("ID Query Failed:");
            Console.WriteLine(E.Message);
            Console.WriteLine();
        }

        try {
            var l1 = db.Books.Where(b => BooksFromIdQuery.Select(b_inner => b_inner.id).Contains(b.id)).ToList();
            Console.WriteLine("ID Query With Whole Book Local Var Worked: count = {0}", l1.Count);
        } catch (Exception E) {
            Console.WriteLine("ID Query With Whole Book Local Var Failed:");
            Console.WriteLine(E.Message);
            Console.WriteLine();
        }

        try {
            var l1 = db.Books.Where(b => BooksFromIdQuery.Contains(b)).ToList();
            Console.WriteLine("Whole Book sub query without select worked: count = {0}", l1.Count);
        } catch (Exception E) {
            Console.WriteLine("Whole Book sub query without select:");
            Console.WriteLine(E.Message);
            Console.WriteLine();
        }

        Console.WriteLine();  

【问题讨论】:

  • 您能否发布更完整的示例来重现您的问题?在我的尝试中,这两个语句在 .NET 4.0 中都可以正常工作。对于 .NET 3.5 作为目标,两个语句都会产生异常,但会产生另一个异常。
  • 完成,所有方法都在那里。感谢您查看此内容
  • 为了 100% 清楚,我只在 .NET 4 上运行过这个
  • @Adam Rackis:在我开始重现您的问题之前,我想建议您查看我之前的两个答案,其中描述了如何做与示例中相同的事情,但更容易. The answer 描述了主要思想。 The demo from the answer 根据用户输入动态构造 EF 查询。
  • 呃 - 我可能应该告诉你 Property 是抽象的,还有一些其他类都继承自 property(因此查询中的 OfType() 。尽管如此,不管数据是什么模型,我一生都无法理解为什么将子步骤拉出到局部变量中会有所作为。

标签: c# entity-framework entity-framework-4 iqueryable


【解决方案1】:

错误消息指出它只支持原始类型。

在您指定的有效代码中,它是IQueryable&lt;long&gt;

我的猜测是不起作用的代码使用了IQueryable&lt;decimal&gt;,因此出现了强制转换错误。

您正在返回一个标识列。一个标识列可以有多种类型。 Decimal 是可以处理所有可能的标识类型的数据类型。

Why does select SCOPE_IDENTITY() return a decimal instead of an integer?

在有效的代码中,编译器会提示使用 long。

【讨论】:

  • 两个调用都使用完全相同的代码。不同之处在于第一个要经过显式声明中间查询的额外步骤。出于某种原因,EF 需要此声明,即使 PropertyQuery.Select(p =&gt; p.PropertyId) 在这两种情况下都将被视为 IQueryable
  • @Adam,我已经更新了我的答案,我认为这与 EF 如何处理身份列有关
【解决方案2】:
f => PropertyQuery.Select(p => p.PropertyId).Contains(f.PropertyId)

上面是一个linq Expression,f=>之后的都是一个表达式树。 Composite Linq 只能根据表达式展开查询,不能根据委托展开查询。

您的两个语句集在逻辑上都是正确的,但从编译器的角度来看它们是不同的。如果您注意到您的扩展(where on where 或 select)仅适用于相同类型的模板参数。您的 int 的 IQueryable 将在哪里不起作用,因为您的 linq 期望 T 的 IQueryable。

其次,当您在 T 上执行 Select 并返回 T 的 IQueryable 时,运行时无法知道早期的 T 是什么类型。

简而言之,将中间步骤保存为局部变量会破坏您的表达式树。我建议您查看 Reflector 以查看实际生成的源代码。

您的整个 lambda 表达式实际上是使用表达式节点构建的,整个树被构建并返回给 Where 方法。在您的第一个示例中,表达式树的其他地方有所不同,它涉及在表达式中执行其他内容。

【讨论】:

  • +1 的好答案,但正如我上次编辑所显示的那样,这可能不是。
  • 好的,从我上次的编辑来看,我认为您的回答基本上是正确的。当(且仅当)您在查询中存在 OfType 时,EF 似乎不需要任何子查询。
【解决方案3】:

尝试编写第一个查询,而不是

IQueryable<long> propertyIdQuery = PropertyQuery.Select(p => p.PropertyId);

var propertyIdQuery = PropertyQuery.Select(p => p.PropertyId);

它会抛出错误吗?这是对我的查询中唯一明显的区别。

【讨论】:

  • 感谢您的回答。从编译器的角度来看,这两行是 100% 相同的,我相当肯定这两行都不会崩溃。从我的问题看来,EF 中的 ExpressionTree 解析机制似乎希望将子查询分解为更简单的部分,就像您在上面粘贴的一样。我想知道的是为什么 - 我想我要把这个 Q 发送给 EF 团队,看看他们是否愿意看。为您的时间 +1 :)
  • 好的,请告诉我们您的发现。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-05
  • 2015-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多