Ошибка приведения Entity Framework - PullRequest
6 голосов
/ 25 февраля 2011

Отлично работает следующее:

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.IQueryable 1' to type 'System.Linq.IQueryable 1'. LINQ to Сущность поддерживает только приведение сущностей Типы примитивов модели данных.

Может ли кто-нибудь пролить свет на то, что EF (4) делает под прикрытием, чтобы заставить работать только первый запрос, даже если они якобы эквивалентны?

Я знаю, IQueryable<T> и деревья выражений много чего делают под покровом, но как получается, что сохранение промежуточного шага в локальную переменную может повлиять на результат?

EDIT

По запросу вот полный вызываемый метод и методы, вызываемые этим методом:

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

EDIT

Я действительно хотел, чтобы ответ Акаша был правильным, но если бы это было так, я бы ожидал, что средний запрос здесь взорвется, но на самом деле все три работают просто отлично.

Я начинаю подозревать, что структура наследования, которую я имею для типа 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();
        }

EDIT

Я добавил некоторое наследование, и теперь два нижних запроса не выполняются. Похоже, что в любой момент, когда у вас есть OfType() в запросе, EF просто не хочет анализировать полные запросы в запросах; Вы должны разбить свои подэтапы на локальные переменные.

Сегодня вечером я назначу награду Акашу, если у кого-то еще не будет чего добавить.

        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();  

Ответы [ 3 ]

1 голос
/ 03 марта 2011

Попробуйте написать первый запрос, но вместо

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

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

Выдает ли это ошибку?Это единственное очевидное отличие в запросах ко мне.

1 голос
/ 12 апреля 2011
f => PropertyQuery.Select(p => p.PropertyId).Contains(f.PropertyId)

Выше - выражение linq, все, что после f => - это дерево выражений.Составной Linq может расширять запрос только на основе выражений, но не делегатов.

Оба ваших набора операторов логически верны, но с точки зрения компилятора они различны.Если вы заметили, что ваш расширенный (где, где или выберите) будет работать только с тем же типом параметра шаблона.В противном случае ваш IQueryable из int не будет работать, так как ваш linq ожидает IQueryable от T.

Во-вторых, когда вы выполняете Select для T и возвращаете IQueryable из T, нет способа выполнения, чтобы узнать, что ранее Tбыл какого типа.

Короче говоря, сохранение промежуточного шага как локальной переменной нарушает ваше дерево выражений.Я предлагаю вам взглянуть на Reflector, чтобы увидеть исходный код того, что на самом деле сгенерировано.

Все ваше лямбда-выражение фактически построено с использованием узла выражения, а все дерево построено и возвращено методу Where.Если в вашем первом примере дерево выражений отличается, оно включает в себя выполнение чего-то еще внутри выражения.

1 голос
/ 25 февраля 2011

В сообщении об ошибке говорится, что оно поддерживает только примитивные типы.

В работающем коде вы указали, что это IQueryable<long>.

Я предполагаю, что код, который неРабота использует IQueryable<decimal>, поэтому ошибка приведения.

Вы возвращаете столбец идентификаторов.Столбец Identity может иметь несколько типов.Десятичное число - это тип данных, который может обрабатывать все возможные типы идентификаторов.

Почему функция SCOPE_IDENTITY () возвращает десятичное число вместо целого?

В работающем кодекомпилятор получает подсказку на использование long.

...