none
Проблема с передачей IQueryable к клиенту RRS feed

  • Вопрос

  • Здравствуйте. Я разрабатываю Silverlight-приложение и использую EF для доступа к данным. Возникла проблема с передачей результата от серверной части приложения к клиентской. Мне необходимо вернуть клиенту результат исполнения хранимой процедуры. В начале я попытался использовать стандартный вариант с добавлением существующей stored procedure в EDMX серверной части приложения, созданием функции импорта на её основе и комплексного типа, как типа возвращаемого этой функцией. В коде класса Domain Service соответственно создал код:

    [Invoke]
    [Query]
    public IQueryable<ct_AWC> Get_Func()
    {
                //return this.ObjectContext.FuncGetAWC().ToList().AsQueryable();
                ObjectResult<ct_AWC> coll = ObjectContext.FuncGetAWC();
                List<ct_AWC> li = coll.ToList();
                IQueryable<ct_AWC> iqcl = li.AsQueryable();
                return iqcl;
    }

    Клиент на одной из форм имеет:

    <riaControls:DomainDataSource AutoLoad="False" d:DesignData="{d:DesignInstance my1:ct_AWC, CreateList=true}" Height="0" LoadedData="ct_AWCDomainDataSource_LoadedData" Name="ct_AWCDomainDataSource" QueryName="Get_FuncQuery" Width="0">
                    <riaControls:DomainDataSource.DomainContext>
                        <my:SkukeDomainContext />
                    </riaControls:DomainDataSource.DomainContext>
    </riaControls:DomainDataSource>

    Я по событию нажатия кнопки стартую: ct_AWCDomainDataSource.Load();

    При выходе из Get_Func() функция возвращает корректное iqcl с данными в требуемом количестве и состоянии, но затем клиент выдаёт ошибку: "Удалённый сервер возвратил ошибку: NotFound"...

    Чтобы попытаться найти источник этой ошибки, я изменил данный подход и сделал следующее:

    1. Написал ещё одну функцию  в коде класса Domain Service:

    public IQueryable<clAWC> GetAWO_IQueryable() 
            {
                //return this.ObjectContext.ExecuteStoreQuery<clAWC>("exec АбонентыБезСчётчиков_Новый").ToList().AsQueryable();
                ObjectResult<clAWC> coll = ObjectContext.ExecuteStoreQuery<clAWC>("exec АбонентыБезСчётчиков_Новый");
                List<clAWC> li = coll.ToList();
                IQueryable<clAWC> iqcl = li.AsQueryable();
                return iqcl;
            }

    2. Создал класс в серверной части приложения:

     public class clAWC
        {
            [Key]
            public Int32 ID { get; set; }        
            public string ЛицевойСчёт { get; set; }
            public string ФИОАбонента { get; set; }
            string Пункт { get; set; }
            string Наименование { get; set; }
            string НомерДома { get; set; }
            string ЛитераДома { get; set; }
            string КорпусДома { get; set; }
            string НомерКвартиры { get; set; }
            string ЛитераКвартиры { get; set; }
            string Сектор { get; set; }
        }

    3. На клиенте создал в обработчике нажатия кнопки:

    SkukeDomainContext sdc = new SkukeDomainContext();
    lo = sdc.Load(sdc.GetAWO_IQueryableQuery(), LoadBehavior.RefreshCurrent, true);            
    lo.Completed += new EventHandler(lo_Completed);
    и
    void lo_Completed(object sender, EventArgs e)
    {
        throw new NotImplementedException();
    }

    Здесь lo объявлена глобально так:

    LoadOperation<clAWC> lo;

    В результате исполнения этого варианта выяснилось следующее:

    1. Функция GetAWO_IQueryable (также как и Get_Func в первом варианте) возвращает iqcl в требуемом виде

    2. В точке останова в lo_Completed на генерации исключения оказалось, что sender.Entities содержит количество сущностей соответствующее количеству сущностей в iqcl на сервере, но каждая из этих сущностей в отличие от iqcl  содержит одну и ту же запись из возвращаемого набора данных (как выяснилось первую), т.е. возвратилась лишь первая запись повторённая столько раз, сколько было записей в iqcl.

    3. Прошу заметить, что в классе clAWC только 3 свойства имеют public. Это связано с тем, что как только я ставлю ещё public любому другому свойству, то sender.Entities перестаёт принимать даже описанное в пункте 2 и количество в нем становится равным 0, несмотря на то, что iqcl всё также содержит и возвращается полный набор данных. При использовании в первом варианте комплексного типа ct_AWC все свойства в нём имели public, поэтому думаю, что генерация клиентом упомянутой выше ошибки NotFound как-то связана именно с этим.

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

    К модераторам: если я ошибся веткой, и тему надо было разместить в ветке "Веб", то прошу перенести эту тему туда... Спасибо.





Ответы

  • Сделан шаг вперёд - избавился от исключения. Согласно теме ТУТ в web.config установка квоты MaxItemsInObjectGraph в моём случае должна была быть такой:

    <system.serviceModel>    
        <services>
          <service name="sba_seti_skuke.Web.DomainModel.SkukeDomainService"  behaviorConfiguration="sba_seti_skuke.Web.SkukeDomainService">
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>                
            <behavior name="sba_seti_skuke.Web.SkukeDomainService">
              <serviceMetadata httpGetEnabled="True"  />
              <serviceDebug includeExceptionDetailInFaults="true" />
              <dataContractSerializer maxItemsInObjectGraph="2147483647"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>
      </system.serviceModel>

    Это привело к уходу генерации исключения и в первом, и во втором вариантах....

    Но не решило другую проблему: по первому варианту клиент получает тольку одну запись:

    (((System.Windows.Controls.DomainDataSource)(sender)).DataView).Count = 1

    а по второму по-прежнему:

    ((System.ServiceModel.DomainServices.Client.LoadOperation<sba_seti_skuke.Web.DomainModel.clAWC>)(sender)).Entities.Count = 14746

    но с продублированной в них одной и той же записью....

    • Помечено в качестве ответа Abolmasov Dmitry 3 июля 2012 г. 12:14
  • Проблема решена. Последняя сложность корнями уходила в следующее: при использовании комплексного типа ct_AWC, как результата от функции импорта, требовалось наличие ключевого свойства, без которого проект не компилировался. Аналогичное требование накладывалось и к самописному классу clAWC. Чтобы обойти это требование, я создал internal sealed class ct_AWCMetadata для ct_AWC, в котором в качестве ключа указал ID, который в хранимой проце изначально не формировался, но я его добавил как "Select  ..., 0 as ID ....". Для clAWC просто в классе ввёл key, как это указано в стартовом топике. В результате серверная часть приложения формировала полноценные List<T> и IQueryable<Т>, но у всех элементов которых все ID=0... Вот видимо по нему и формировались единичные сущности на клиенте. Решением стала ликвидация везде упоминания ID, и переделка атрибута KEY на поле "ЛицевойСчет" в метаданных для ct_AWC и в классе для clAWC. После этого все упомянутые выше варианты отработали также корректно с серверной стороны, а клиент сформировал правильную коллекцию в DomainSource и отобразил в гриде.

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

    • Помечено в качестве ответа Abolmasov Dmitry 3 июля 2012 г. 12:10
    3 июля 2012 г. 11:10

Все ответы

  • Пока я начну разбираться в коде, уже начал, Вы со своей стороны попробуйте включить трассировку в WCF и используя Средство редактирования конфигурации, а потом посмотрите, какая именно ошибка выскакивает, так как под NotFound может много чего скрываться.
    2 июля 2012 г. 12:21
    Модератор
  • Спасибо за ссылки к информации на эти инструменты - это для меня новое, и пока ещё не до конца понятое... Я попытался сделать файл лога на ошибку по первому варианту, и получилось вот что: http://rusfolder.com/31447302

    Ошибка при генерировании исключения выглядит так: "Ошибка сериализации параметра http://tempuri.org/:Get_FuncResult. Сообщение InnerException было "Максимальное число объектов, которые могут быть сериализованы или десериализованы в графе объекте, равно "65536". Измените граф объекта или увеличьте квоту MaxItemsInObjectGraph. "

    Соответственно и вопрос, а как исправить именно это?

    Для второго варианта никаких исключений в логе не видно, и остаётся непонятной разница между iqcl, возвращаемым серверной частью, и sender.Entities на клиенте, которые по идее должны бы быть идентичны.
  • Попробуйте увеличить квоту, вот тут написано как. Как я понял у Вас clAWC обычный POCO класс. Попробуйте вернуть клиенту не тип

    IQueryable

    а обычный массив.

    clAWC[] li = coll.ToArray();
    return li;

    Вы получаете данные физически, а потом преобразуете в дерево(запрос), а это уже не нужно. Т.к. сервис у Вас конечная точка, оттуда нужно отправлять уже готовые данные, а тип IQueryable это не сами данные, а запрос.


    Модератор
  • Квоту я попытался увеличить, добавив в web.config серверной части секцию:

    <system.serviceModel>    
        <behaviors>
          <serviceBehaviors>
            <behavior name="MyServiceBehavior">
              <dataContractSerializer maxItemsInObjectGraph="2147483647" />
            </behavior>
          </serviceBehaviors>
        </behaviors>
      </system.serviceModel>

    Но в логе идёт та же самая ошибка со значением 65536... Вот мой web.config: http://rusfolder.com/31448058. Возможно, что на клиенте тоже это значение надо изменить, но как это сделать для клиентской части silverlight-приложения?

    Для второго варианта сделал функцию:

    [Query]
    public List<clAWC> GetAWO_List()
      {
                ObjectResult<clAWC> coll = ObjectContext.ExecuteStoreQuery<clAWC>("exec АбонентыБезСчётчиков_Новый");
                List<clAWC> li = coll.ToList();            
                return li;
      }
    И вызываю её также:
    lo = sdc.Load(sdc.GetAWO_ListQuery(), LoadBehavior.RefreshCurrent, true);
    Ну и в итоге вернувшийся к клиенту результат аналогичен: GetAWO_List возвращает корректный li, а на клиенте sender.Entities такой же как и ранее...

  • А при использовании второго варианта, т.е. когда возвращаете

    [Query]
    public List<clAWC> GetAWO_List()
      {
                ObjectResult<clAWC> coll = ObjectContext.ExecuteStoreQuery<clAWC>("exec АбонентыБезСчётчиков_Новый");
                List<clAWC> li = coll.ToList();            
                return li;
      }

    ошибка с сериализацией происходит? И много ли данных Вы отправляете?

    Модератор
  • при использовании второго варианта ошибок сериализации нет ни при возврате List, ни при возврате IQueryable... возвращается 14746 записей, которые содержатся соотвественно в li и в iqcl на серверной стороне....  разница между классом clAWC для второго варианта и комплексным типом ct_AWC для первого варианта только в наличии public для всех свойств у ct_AWC... если для clAWC также выставить всем свойствам public, то клиент по второму варианту не получает этих 14746 записей и количество sender.Entities равно 0....
  • Т.е уже выходит, что данные нормально отправляются и клиент их получает? Старайтесь свойства которые должны сериализоваться помечать модификатором public, а если нужна частичная сериализация, то применяйте атрибут DataContract.
    Модератор
  • Проблема вот в чём: данные отправляются нормально, а клиент их корректно не получает ни в каком варианте. Разницы между первым и вторым вариантом нет, если классу clAWC поставить модификатор public для всех свойств - в первом варианте происходит генерация исключения, а во втором - просто нет данных в sender.Entities без генерации исключения (вернее так: исключение произошло, но клиент не выдаёт его в виде сообщения, и его можно обнаружить только вручную в свойстве  (((System.ServiceModel.DomainServices.Client.OperationBase)(sender)).Error).Message).    "Load operation failed for query 'GetAWO_List'. Удаленный сервер возвратил ошибку: NotFound."

    Если же класс clAWC оставить в том виде, как в стартовом топике, то sender.Entities получает одну запись и дублирует её по количеству записей вданных, отправленных серверной частью... В итоге ни в одном варианте клиент не получает данные в корректном виде....

  • Попробуйте используя вот этот инструмент, посмотреть что же на самом деле возвращается на клиент. Может просто десериализация не верно выполняется на клиенте, а сами данные доходят нормально.
    Модератор
  • Сделан шаг вперёд - избавился от исключения. Согласно теме ТУТ в web.config установка квоты MaxItemsInObjectGraph в моём случае должна была быть такой:

    <system.serviceModel>    
        <services>
          <service name="sba_seti_skuke.Web.DomainModel.SkukeDomainService"  behaviorConfiguration="sba_seti_skuke.Web.SkukeDomainService">
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>                
            <behavior name="sba_seti_skuke.Web.SkukeDomainService">
              <serviceMetadata httpGetEnabled="True"  />
              <serviceDebug includeExceptionDetailInFaults="true" />
              <dataContractSerializer maxItemsInObjectGraph="2147483647"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>
      </system.serviceModel>

    Это привело к уходу генерации исключения и в первом, и во втором вариантах....

    Но не решило другую проблему: по первому варианту клиент получает тольку одну запись:

    (((System.Windows.Controls.DomainDataSource)(sender)).DataView).Count = 1

    а по второму по-прежнему:

    ((System.ServiceModel.DomainServices.Client.LoadOperation<sba_seti_skuke.Web.DomainModel.clAWC>)(sender)).Entities.Count = 14746

    но с продублированной в них одной и той же записью....

    • Помечено в качестве ответа Abolmasov Dmitry 3 июля 2012 г. 12:14
  • А у Вас с сервера точно отправляются не продублированные данные? Чтобы убедиться в этом, используйте Fiddler, как я предложил высше.
    Модератор
  • Сейчас попробую Fiddler тоже, но пока на точках останова в функциях серверной части на return и li, и iqcl заполнены всеми данными корректно.

  • Запустил фидлер... Наблюдаю следующее:

    в инспекторе заголовок: GET /ClientBin/sba_seti_skuke-Web-DomainModel-SkukeDomainService.svc/binary/GetAWO_IQueryable HTTP/1.1

    в разделах, например, WebView и Raw перечислены все возвращённые записи: Document is 5204602 bytes

    Имеющиеся варианты:
    public IQueryable<clAWC> GetAWO_IQueryable() 
            {            
                ObjectResult<clAWC> coll = ObjectContext.ExecuteStoreQuery<clAWC>("exec АбонентыБезСчётчиков_Новый");
                List<clAWC> li = coll.ToList();
                IQueryable<clAWC> iqcl = li.AsQueryable();
                return iqcl;
            }
    
    [Query]
    public List<clAWC> GetAWO_List()
            {
                ObjectResult<clAWC> coll = ObjectContext.ExecuteStoreQuery<clAWC>("exec АбонентыБезСчётчиков_Новый");
                List<clAWC> li = coll.ToList();            
                return li;
            }
    
    [Invoke]
    [Query]
            public IQueryable<ct_AWC> Get_Func()
            {
                ObjectResult<ct_AWC> coll = ObjectContext.FuncGetAWC();
                List<ct_AWC> li = coll.ToList();
                IQueryable<ct_AWC> iqcl = li.AsQueryable();
                return iqcl;
            }
    теперь все возвращают один и тот же требуемый набор данных: 14746 разных записей в виде коллекции... Клиент же в любом варианте по событию LoadedData в DomainSource содержит в sender.DataView одну запись, которую и отображает на форме в гриде, и в e.AllEntities содержит 14746 одинаковых записей, соответствующих первой в серверной части.
  • Проблема решена. Последняя сложность корнями уходила в следующее: при использовании комплексного типа ct_AWC, как результата от функции импорта, требовалось наличие ключевого свойства, без которого проект не компилировался. Аналогичное требование накладывалось и к самописному классу clAWC. Чтобы обойти это требование, я создал internal sealed class ct_AWCMetadata для ct_AWC, в котором в качестве ключа указал ID, который в хранимой проце изначально не формировался, но я его добавил как "Select  ..., 0 as ID ....". Для clAWC просто в классе ввёл key, как это указано в стартовом топике. В результате серверная часть приложения формировала полноценные List<T> и IQueryable<Т>, но у всех элементов которых все ID=0... Вот видимо по нему и формировались единичные сущности на клиенте. Решением стала ликвидация везде упоминания ID, и переделка атрибута KEY на поле "ЛицевойСчет" в метаданных для ct_AWC и в классе для clAWC. После этого все упомянутые выше варианты отработали также корректно с серверной стороны, а клиент сформировал правильную коллекцию в DomainSource и отобразил в гриде.

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

    • Помечено в качестве ответа Abolmasov Dmitry 3 июля 2012 г. 12:10
    3 июля 2012 г. 11:10
  • Значит косяк был всё же на сервере. Всегда рад и готов помочь.
    3 июля 2012 г. 11:35
    Модератор
  • Спасибо за подробное описание решения. Оно будет очень полезным для тех, кто столкнется с подобной задачей.


    Для связи [mail]

    3 июля 2012 г. 12:21