none
LinqToSql вывод данных, картинки... тормоза... как правильно? RRS feed

  • Вопрос

  • День добрый!

    подскажите пожалуйста как правильно делать вывод данных из связанных таблиц на DataGridView...

    у меня есть основная таблица допустим Cars... в этой таблице много разных данных...

    мне необходимо вывести на форму DataGrid в котором содержался бы некий набор данных из этой таблицы, НО помимио этого необходимо выводить столбцы из подчинённых таблиц... к примеру PurchaseOrders (таблица поступлений) нужно вывести схему поступления и приёмщика, но и это ещё не всё... мне необходимо вывести столбец к примеру обращений клиентов по данному автомобилю... т.е. это ещё одна таблица IncomeCalls в которую складируются звонки, а со звонками связаны анкеты заполненные колл-центром... в анкетах можно выцепить что звонок именно по выводимому авто...

    в принципе через LinqToSql я всё это делаю пробегая по списку автомобилей и при выводе того или иного автомобиля подсчитываю сколько обращений, сколько того сколько сего, кто приёмщик обращаясь car.PurchaseOrders.First().ManagerName и т.д. и т.п....

    всё бы ничего... вывожу таблицу в 15-20 столбцов и в 200 строк максимум... но чем дальше тем дольше грузится грид и сейчас я дошёл до того что добавляя очередной столбец (это отдельный вопрос) я получаю ошибку что слишком долго выполняется формирование и иди ты лесом...

    так вот задался я вопросом... как же всё-таки правильно выводить казалось бы такую небольшую таблицу... 

    [u][b]1)[/b] как убыстрить вывод? т.е. как правилньо выводить из БД такой набор разных данных из разных таблиц...
    [b]2)[/b] как вывести картинку в таблицу (считается что если в подчиненной таблице есть фотографии для автомобиля, то вывод картинки с фотоаппаратом, если фоток нет - то не выводить ничего)
    [/u]
    Буду весьма благодарен за советы...
    20 января 2014 г. 13:00

Ответы

  • может быть мне надо использовать каую-нибудь подобную структуру запроса?

    Почему бы и нет. Но суть linq2sql в том, что автоматически отслеживаются загруженные сущностные объекты, и если в них произошли изменения, то при вызове SubmitChanges будут сохранены в БД только изменённые данные.

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

    • Помечено в качестве ответа ITAlex 22 января 2014 г. 8:01
    22 января 2014 г. 7:51
  • car.CarPhotos.Count

    В этом выражении происходит загрузка всех фотографий, относящихся к этому автомобилю. И уже на клиенте берётся их количество. Неудивительно, что загрузка изображений требует много времени.

    -----

    Сейчас я ухожу, позже ещё посмотрю код, что-нибудь посоветую.

    Пока отмечу следующее: вот такие выражения

    Select(o => o)

    можно убрать - они ничего не делают.

    -----

    Чтобы посмотреть, какие sql-запросы формируются, можно добавить в код строку:

    dc.Log = Console.Out;
    сразу после создания датаконтекста. И в окне Output Студии в режиме Debug будут выводиться запросы.
    • Помечено в качестве ответа ITAlex 22 января 2014 г. 8:02
    21 января 2014 г. 10:04
  • По умолчанию, в LinqToSql загрузка данных происходит отложенно - лениво. То есть данные будет загружаться только тогда, когда в них действительно есть необходимость. Иногда это избавляет от загрузки не нужных данных, но ухудшает общую производительность в том случае, если эти данные нужны.

    Можно включить немедленную - энергичную загрузку. По части схемы и коду я не смог до конца понять все связи. Единственно ясно вижу, что свойство CarPhotos является EntitySet. Правильно? В таком случае, энергичную загрузку можно включить так:

    using (var dc = new K20AppDataContext()) { //dc.Log = Console.Out; var options = new DataLoadOptions(); //options.LoadWith<Car>(car => car.CarPhotos); options.LoadWith<Car>(car => car.ConfigModels); options.LoadWith<Car>(car => car.ConfigBrands); dc.LoadOptions = options;


    Метод LoadWith можно (нужно) применить столько раз, сколько есть свойств EntitySet у класса Car.

    Уточню, что сами фотографии не стоит загружать энергично, т. к. их объём велик, а вот любые другие свойства - желательно.

    -----

    Вместо вот этой строки

    _cell_photo.Value = car.CarPhotos.Count;

    лучше написать примерно так:

    dc.CarPhotos.Where(p => p.CarId == car.Id).Count();

    При этом сами фотки не загружаются на клиент, а подсчёт их количество происходит в БД.

    -----

    Я смотрю на огромное количество свойств класса Car, и смотрю на код sql-запросов. У меня создаётся впечатление, что большая часть этих свойств является ключами для связи с другими таблицами. Если я не ошибаюсь, то это неправильно. Ключ один - Car.Id. И именно по этому значению должна происходить связь со всеми другими таблицами.

    Ну то есть, если указан id автомобиля 2157, то он же должен фигурировать в запросах к ConfigModels, ConfigBrands и т. д., но там я почему-то вижу неизвестно откуда взявшиеся числа 430, 4...

    Похоже, нормализация БД проведена не до конца.

    • Помечено в качестве ответа ITAlex 22 января 2014 г. 8:01
    22 января 2014 г. 0:41
  • разве так не правильно делать?


    В большинстве случаев так. То что вы описываете, называется нормализация. Основной лозунг БД: "Одна информация - хранится в одном месте". Но за все нужно платить. В некоторых случаях будут дольше выборки, увеличивается сложность для понимания и т.д. Но в 98% случаев надо делать именно так, как вы написали.
    • Помечено в качестве ответа ITAlex 22 января 2014 г. 8:01
    22 января 2014 г. 6:53
    Отвечающий
  • разве так не правильно делать?
    Правильно. Я, похоже, не с той стороны на связи посмотрел, т. к. всей информацией не обладаю.
    • Помечено в качестве ответа ITAlex 22 января 2014 г. 8:01
    22 января 2014 г. 7:20
  • Кстати да, можно воспользоваться анонимным классом, чтобы картинки не тянуть, просто на стороне SQL проверять есть она или нет и возвращать на клиента булево значение.
    • Помечено в качестве ответа ITAlex 22 января 2014 г. 8:01
    22 января 2014 г. 7:57
    Отвечающий

Все ответы

  • Приведите чуть больше информации. Какая СУБД используется? (Думаю, MS Sql Server).

    Какой тип приложения? Desktop или что? Какой GUI? (Судя по DataGridView - WinForms).

    Используется именно Linq2Sql, не EF?

    Хотелось бы видеть код. Как код добавления столбцов к датагриду, так и код запросов. Происходит ленивая или энергичная загрузка?

    Но главное - схема БД. Какие таблицы как связаны между собой. И сколько данных в таблицах (тысячи строк? миллионы?)

    Судя по написанному, в ходе запроса происходят многочисленные join'ы. Возможно, помогут индексы.

    20 января 2014 г. 18:26
  • Приведите чуть больше информации. Какая СУБД используется? (Думаю, MS Sql Server).

    Да вы правы MS SQL 2008 R2

    Какой тип приложения? Desktop или что? Какой GUI? (Судя по DataGridView - WinForms).

    и тут правы WinForms

    Используется именно Linq2Sql, не EF?

    using System.Linq;

    Хотелось бы видеть код. Как код добавления столбцов к датагриду, так и код запросов. Происходит ленивая или энергичная загрузка?

    ленивая/энергичная прошу прощения за ламерский вопрос... это как?... с объектами на форме почти ничего не делал.... просто кинул грид и в модуль функции писать...

                using (var dc = new K20AppDataContext())
                {
                    var CarsQuery = dc.Cars.Where(c => c.Status != "Продан" && c.Status.ToString() != "" && c.Status.ToString() != "Возврат").OrderBy(p => p.Id).Select(o => o);
    
    //далее основываясь на фильтре выборка уточняется следующим образом... привожу пример... таких уточнющих фильтров может быть штук 10...
    
                    if (cfilter.uch_CarID.Checked)
                    {
    
                        List<int> CarIDs_ToFind = cfilter.Filter_CarIDs.CheckedItems.OfType<int>().ToList();
                        if (CarIDs_ToFind.Count > 0) CarsQuery = CarsQuery.Where(p => CarIDs_ToFind.Contains(p.Id));
                    }
    
    //ну и самое последнее это способ занесения данных...
    
                    foreach (var car in CarsQuery)
                    {
                        try
                        {
                            int CallsCount = dc.Anketas.Where(c => c.Quation == "ID автомобиля" && c.Answer == car.Id.ToString()).Select(o => o).Count();
                            int FPKsCount = dc.FPKs.Where(c => c.FPK_Car == car.Id).Select(o => o).Count();
                            DataGridViewRow _row = new DataGridViewRow();
                            DataGridViewCell _cell1 = new DataGridViewTextBoxCell(); _cell1.Value = car.Id; _row.Cells.Add(_cell1);
                            //DataGridViewCell _cell_photo = new DataGridViewTextBoxCell(); _cell_photo.Value = car.CarPhotos.Count; _row.Cells.Add(_cell_photo);
    
    //вот тут как видно я пытался вывести ещё один столбец для последующего вывода картинки если значение более 0... но при выводе этого столбца уже сваливается программа
    
                            DataGridViewCell _cell2 = new DataGridViewTextBoxCell(); _cell2.Value = car.RfidNum; _row.Cells.Add(_cell2);
                            DataGridViewCell _cell3 = new DataGridViewTextBoxCell(); _cell3.Value = car.ConfigModel.ConfigBrand.Name; _row.Cells.Add(_cell3);
                            DataGridViewCell _cell4 = new DataGridViewTextBoxCell(); _cell4.Value = car.ConfigModel.Name; _row.Cells.Add(_cell4);
                            DataGridViewCell _cell5 = new DataGridViewTextBoxCell(); _cell5.Value = car.VIN; _row.Cells.Add(_cell5);
                            DataGridViewCell _cell6 = new DataGridViewTextBoxCell(); _cell6.Value = car.ConfigLocation.Name; _row.Cells.Add(_cell6);
                            DataGridViewCell _cell7 = new DataGridViewTextBoxCell(); _cell7.Value = (car.ConfigParking != null ? car.ConfigParking.Parking : ""); _row.Cells.Add(_cell7);
                            DataGridViewCell _cell8 = new DataGridViewTextBoxCell(); _cell8.Value = dc.PurchaseOrders.Where(q => (int)q.PurchaseOrder_Car == car.Id).First().BuyScheme; _row.Cells.Add(_cell8);
                            DataGridViewCell _cell9 = new DataGridViewTextBoxCell(); _cell9.Value = car.Status; _row.Cells.Add(_cell9);
                            DataGridViewCell _cell10 = new DataGridViewTextBoxCell(); _cell10.Value = (car.ReserveDate.HasValue ? car.ReserveDate.Value.ToShortDateString() : ""); _row.Cells.Add(_cell10);
                            DataGridViewCell _cell11 = new DataGridViewTextBoxCell(); _cell11.Value = car.ReserveManager; _row.Cells.Add(_cell11);
                            DataGridViewCell _cell12 = new DataGridViewTextBoxCell(); _cell12.Value = (DateTime.Today - (DateTime)car.InSaleDate).Days; _row.Cells.Add(_cell12);
                            DataGridViewCell _cell13 = new DataGridViewTextBoxCell(); _cell13.Value = car.Year; _row.Cells.Add(_cell13);
                            DataGridViewCell _cell14 = new DataGridViewTextBoxCell(); _cell14.Value = car.Mileage; _row.Cells.Add(_cell14);
                            DataGridViewCell _cell15 = new DataGridViewTextBoxCell(); _cell15.Value = Convert.ToInt32(car.PriceRUB.Value); _row.Cells.Add(_cell15);
                            DataGridViewCell _cell16 = new DataGridViewTextBoxCell(); _cell16.Value = CallsCount.ToString() + " / " + FPKsCount.ToString(); _row.Cells.Add(_cell16);
                            DataGridViewCell _cell17 = new DataGridViewTextBoxCell(); _cell17.Value = car.BodyType; _row.Cells.Add(_cell17);
                            DataGridViewCell _cell18 = new DataGridViewTextBoxCell(); _cell18.Value = car.ConfigColor.Name; _row.Cells.Add(_cell18);
                            DataGridViewCell _cell19 = new DataGridViewTextBoxCell(); _cell19.Value = car.GearBox; _row.Cells.Add(_cell19);
                            DataGridViewCell _cell20 = new DataGridViewTextBoxCell(); _cell20.Value = car.EngineType; _row.Cells.Add(_cell20);
                            DataGridViewCell _cell21 = new DataGridViewTextBoxCell(); _cell21.Value = car.EngineVolume; _row.Cells.Add(_cell21);
                            DataGridViewCell _cell22 = new DataGridViewTextBoxCell(); _cell22.Value = car.EnginePower; _row.Cells.Add(_cell22);
                            DataGridViewCell _cell23 = new DataGridViewTextBoxCell(); _cell23.Value = car.Transmission; _row.Cells.Add(_cell23);
                            DataGridViewCell _cell24 = new DataGridViewTextBoxCell(); _cell24.Value = car.CabinType; _row.Cells.Add(_cell24);
                            DataGridViewCell _cell25 = new DataGridViewTextBoxCell(); _cell25.Value = car.CabinColor; _row.Cells.Add(_cell25);
                            DataGridViewCell _cell26 = new DataGridViewTextBoxCell(); _cell26.Value = car.PurchaseManager; _row.Cells.Add(_cell26);
                            DataGridViewCell _cell27 = new DataGridViewTextBoxCell(); _cell27.Value = ""; _row.Cells.Add(_cell27);
                            
                            CarsGrid.Rows.Add(_row);
    
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show("Ошибка при выводе автомобиля Id = " + car.Id + "\r\r" + ex.Message);
                        }
    
                        RowNum++;
                    }
    
    
    }

    Но главное - схема БД. Какие таблицы как связаны между собой. И сколько данных в таблицах (тысячи строк? миллионы?)

    да собственно говоря таблицы не сильно загруженые... размер бэкапа базы конечно большой порядка 20Гб... но это из-за фоток и прочих документов загружаемых в программу хранящихся в БД в виде двоичных данных... в часности ещё раз повторюсь что на экран итогово выводится таблица как видно из кода в 27 столбцов... а строк практически всего ничего... штук 200-300...

    Судя по написанному, в ходе запроса происходят многочисленные join'ы. Возможно, помогут индексы.

    привести схему сложно... но попытаюсь... это частичный скрин файла DBase.dbml (откройте картинку в новой вкладке... крупнее будет...)




    • Изменено ITAlex 21 января 2014 г. 8:09
    21 января 2014 г. 7:46
  • car.CarPhotos.Count

    В этом выражении происходит загрузка всех фотографий, относящихся к этому автомобилю. И уже на клиенте берётся их количество. Неудивительно, что загрузка изображений требует много времени.

    -----

    Сейчас я ухожу, позже ещё посмотрю код, что-нибудь посоветую.

    Пока отмечу следующее: вот такие выражения

    Select(o => o)

    можно убрать - они ничего не делают.

    -----

    Чтобы посмотреть, какие sql-запросы формируются, можно добавить в код строку:

    dc.Log = Console.Out;
    сразу после создания датаконтекста. И в окне Output Студии в режиме Debug будут выводиться запросы.
    • Помечено в качестве ответа ITAlex 22 января 2014 г. 8:02
    21 января 2014 г. 10:04
  • пока что просмотрел лог... вот что пишет... это получается дёргание базы для вывода одного авто... как можно оптимизировать вывод основываясь на структуре БД что я представил... и по коду...

    спасибо огромное за помощь...

    ------------------------ вывод автомобиля id 2157
    SELECT COUNT(*) AS [value]
    FROM [dbo].[Anketas] AS [t0]
    WHERE ([t0].[Quation] = @p0) AND ([t0].[Answer] = @p1)
    -- @p0: Input NVarChar (Size = 13; Prec = 0; Scale = 0) [ID автомобиля]
    -- @p1: Input NVarChar (Size = 4; Prec = 0; Scale = 0) [2157]
    -- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.4926
    
    SELECT COUNT(*) AS [value]
    FROM [dbo].[FPKs] AS [t0]
    WHERE [t0].[FPK_Car] = @p0
    -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [2157]
    -- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.4926
    
    SELECT [t0].[Id], [t0].[Name], [t0].[Title], [t0].[TMRName], [t0].[AVITOName], [t0].[ConfigModel_ConfigBrand], [t0].[YandexName]
    FROM [dbo].[ConfigModels] AS [t0]
    WHERE [t0].[Id] = @p0
    -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [430]
    -- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.4926
    
    SELECT [t0].[Id], [t0].[Name], [t0].[TMRName], [t0].[AVITOName]
    FROM [dbo].[ConfigBrands] AS [t0]
    WHERE [t0].[Id] = @p0
    -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [4]
    -- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.4926
    
    SELECT [t0].[Id], [t0].[Name], [t0].[Description]
    FROM [dbo].[ConfigLocations] AS [t0]
    WHERE [t0].[Id] = @p0
    -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [6]
    -- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.4926
    
    SELECT [t0].[Id], [t0].[DC], [t0].[Parking], [t0].[Description]
    FROM [dbo].[ConfigParkings] AS [t0]
    WHERE [t0].[Id] = @p0
    -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [9]
    -- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.4926
    
    SELECT TOP (1) [t0].[Id], [t0].[Manager], [t0].[OrderDate], [t0].[OrderSource], [t0].[Status], [t0].[Category], [t0].[CustomerString], [t0].[PhoneString], [t0].[PriceRUB], [t0].[PriceMinRUB], [t0].[PriceMaxRUB], [t0].[DiagnosticsFlag], [t0].[GibddFlag], [t0].[BuyScheme], [t0].[TechCenter], [t0].[TechCenterManager], [t0].[ApprovedDate], [t0].[ApprovedPriceRUB], [t0].[Comments], [t0].[PurchaseOrder_Customer], [t0].[PurchaseOrder_Car], [t0].[CustomerByDov]
    FROM [dbo].[PurchaseOrders] AS [t0]
    WHERE ([t0].[PurchaseOrder_Car]) = @p0
    -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [2157]
    -- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.4926
    
    SELECT [t0].[Id], [t0].[Name]
    FROM [dbo].[ConfigColors] AS [t0]
    WHERE [t0].[Id] = @p0
    -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [23]
    -- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.4926
    
    ------------------------ вывод автомобиля id 2794
    

    21 января 2014 г. 11:25
  • По умолчанию, в LinqToSql загрузка данных происходит отложенно - лениво. То есть данные будет загружаться только тогда, когда в них действительно есть необходимость. Иногда это избавляет от загрузки не нужных данных, но ухудшает общую производительность в том случае, если эти данные нужны.

    Можно включить немедленную - энергичную загрузку. По части схемы и коду я не смог до конца понять все связи. Единственно ясно вижу, что свойство CarPhotos является EntitySet. Правильно? В таком случае, энергичную загрузку можно включить так:

    using (var dc = new K20AppDataContext()) { //dc.Log = Console.Out; var options = new DataLoadOptions(); //options.LoadWith<Car>(car => car.CarPhotos); options.LoadWith<Car>(car => car.ConfigModels); options.LoadWith<Car>(car => car.ConfigBrands); dc.LoadOptions = options;


    Метод LoadWith можно (нужно) применить столько раз, сколько есть свойств EntitySet у класса Car.

    Уточню, что сами фотографии не стоит загружать энергично, т. к. их объём велик, а вот любые другие свойства - желательно.

    -----

    Вместо вот этой строки

    _cell_photo.Value = car.CarPhotos.Count;

    лучше написать примерно так:

    dc.CarPhotos.Where(p => p.CarId == car.Id).Count();

    При этом сами фотки не загружаются на клиент, а подсчёт их количество происходит в БД.

    -----

    Я смотрю на огромное количество свойств класса Car, и смотрю на код sql-запросов. У меня создаётся впечатление, что большая часть этих свойств является ключами для связи с другими таблицами. Если я не ошибаюсь, то это неправильно. Ключ один - Car.Id. И именно по этому значению должна происходить связь со всеми другими таблицами.

    Ну то есть, если указан id автомобиля 2157, то он же должен фигурировать в запросах к ConfigModels, ConfigBrands и т. д., но там я почему-то вижу неизвестно откуда взявшиеся числа 430, 4...

    Похоже, нормализация БД проведена не до конца.

    • Помечено в качестве ответа ITAlex 22 января 2014 г. 8:01
    22 января 2014 г. 0:41
  • на каждые автомобиль хранить своё описание марки и модели по-моему не правильно... или я не прав?...

    имеем мы 100 автомобилей марки VW, из них 25 Polo, 25 Golf и т.п.

    соответственно в базе у нас получается

    в таблице марок одна запись... VW (id 1), в таблице моделей 2 записи Polo (id 1) , Golf (id 2) и у обоих прописано что они связаны с маркой VW (какое нибудь ключевое поле в таблице моделей типа BrandId равное 1)

    в таблице автомобилей получаем 100 записей... в которой каждый автомобиль имеет ключевое значение id и указание на принадлежность моделе автомобиля ModelId к примеру у 25 авто это 1 у других 25 это 2...

    разве так не правильно делать?

    22 января 2014 г. 6:43
  • разве так не правильно делать?


    В большинстве случаев так. То что вы описываете, называется нормализация. Основной лозунг БД: "Одна информация - хранится в одном месте". Но за все нужно платить. В некоторых случаях будут дольше выборки, увеличивается сложность для понимания и т.д. Но в 98% случаев надо делать именно так, как вы написали.
    • Помечено в качестве ответа ITAlex 22 января 2014 г. 8:01
    22 января 2014 г. 6:53
    Отвечающий
  • разве так не правильно делать?
    Правильно. Я, похоже, не с той стороны на связи посмотрел, т. к. всей информацией не обладаю.
    • Помечено в качестве ответа ITAlex 22 января 2014 г. 8:01
    22 января 2014 г. 7:20
  • может быть мне надо использовать каую-нибудь подобную структуру запроса?

    var chassi = (from a in db.Cabinets
                  from b in db.Commodities
                  from e in db.sArticleNumbers
    
                  where
                      kjopKollonne.Contains(e.ArtNum) &&
                      a.ArticleNumberID == e.ID &&
                      a.ArticleNumberID == b.ArticleNumberID
    
                  select new {
                      ArtNum = e.ArtNum,
                      Price = b.Price,
                      ModelName = a.ModelName,
                               }).ToList();

    22 января 2014 г. 7:22
  • может быть мне надо использовать каую-нибудь подобную структуру запроса?

    Почему бы и нет. Но суть linq2sql в том, что автоматически отслеживаются загруженные сущностные объекты, и если в них произошли изменения, то при вызове SubmitChanges будут сохранены в БД только изменённые данные.

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

    • Помечено в качестве ответа ITAlex 22 января 2014 г. 8:01
    22 января 2014 г. 7:51
  • Кстати да, можно воспользоваться анонимным классом, чтобы картинки не тянуть, просто на стороне SQL проверять есть она или нет и возвращать на клиента булево значение.
    • Помечено в качестве ответа ITAlex 22 января 2014 г. 8:01
    22 января 2014 г. 7:57
    Отвечающий
  • спасибо огромное за консультацию :) я стал ещё на один шаг умнее в c# :)
    22 января 2014 г. 8:00
  • ох-хо-хоюшки... вернусь к своим баранам...

                    var CarsQuery = (from cars in dc.Cars
    
                                     let PhotosCount = cars.CarPhotos.Count()
                                     let CallFpkString = dc.Anketas.Where(c => c.Quation == "ID автомобиля" && c.Answer == cars.Id.ToString()).Select(o => o).Count() + "/" + dc.FPKs.Where(c => c.FPK_Car == cars.Id).Select(o => o).Count()
                                     let BuySheme = dc.PurchaseOrders.Where(q => (int)q.PurchaseOrder_Car == cars.Id).First().BuyScheme
                                     let Parking = dc.ConfigParkings.Where(p => p.Id == cars.Parking.Value).First().Parking
                                     let DaysInStock = (DateTime.Today - (DateTime)cars.InSaleDate).Days
                                     let PriceRUB = Convert.ToInt32(cars.PriceRUB.Value)
                                     let Model = cars.ConfigModel.Name
                                     let Brand = cars.ConfigModel.ConfigBrand.Name
                                     let LoactionCar = cars.ConfigLocation.Name
                                     let Color = cars.ConfigColor.Name
    
                                     where
    
                                     (cars.Status == "Свободен" || cars.Status == "ПСО" || cars.Status == "Резерв" || cars.Status == "Предоплата" || cars.Status == "Заморожен") //&&
    
                                     orderby
                                     cars.Id
    
                                     select new { 
                                         CarId = cars.Id,
                                         PhotosCount,
                                         RfidNum = cars.RfidNum,
                                         Brand,
                                         Model,
                                         VIN = cars.VIN,
                                         LoactionCar,
                                         Parking,
                                         BuySheme,
                                         Status = cars.Status,
                                         ReserveDate = cars.ReserveDate,
                                         ReserveManager = cars.ReserveManager,
                                         PredoplataRUB = (cars.PredoplataRUB.HasValue ? Convert.ToInt32(cars.PredoplataRUB.Value) : -1),
                                         DaysInStock,
                                         Year = cars.Year, 
                                         Mileage = cars.Mileage,
                                         PriceRUB,
                                         CallFpkString,
                                         BodyType = cars.BodyType,
                                         Color,
                                         GearBox = cars.GearBox,
                                         EngineType = cars.EngineType,
                                         EngineVolume = cars.EngineVolume,
                                         EnginePower = cars.EnginePower,
                                         Transmission = cars.Transmission,
                                         CabinType = cars.CabinType,
                                         CabinColor = cars.CabinColor,
                                         PurchaseManager = cars.PurchaseManager
    
                                     });

    у меня выводится всего 200 строк из базы...

    CarsGrid.DataSource = CarsQuery;

    но господи... почему же так долго... чтобы вывести 200 строк требуется времени порядка 4 секунд... 

    ещё больше бесит что при попытке сортировки грида (клик на заголовке столбца) время на отработку уходит почти такое же 3-4 секунды...

    как всё же правильно выводить данные пользователю из бд?... объёмы же мизерные... откуда такие тормоза?... как убыстрить работу программы?...

    Заранее благодарю, если кто поможет...

    25 февраля 2014 г. 9:10
  • let PhotosCount = cars.CarPhotos.Count()
    

    В этой строке загружаются все фотографии!

    Заменить на

    let PhotosCount = dc.CarPhotos.Where(p => p.CarId == car.Id).Count()



    CarsGrid.DataSource = CarsQuery;

    Каждый раз при доступе к данным будет происходить запрос к БД.

    Заменить на

    CarsGrid.DataSource = CarsQuery.ToArray();


    В этом случае данные, полученные из запроса, будут сохранены в массиве, и далее берутся из него.

    25 февраля 2014 г. 11:16
  • Мне черезвычайно, т.е. катастрофически не нравится ваш запрос. Вот здесь написано как получить SQL который уходит в базу. Попробуйте этот запрос выполнить в SQL студии и посмотрите его время выполнения.
    25 февраля 2014 г. 11:31
    Отвечающий
  • let PhotosCount = dc.CarPhotos.Where(p => p.CarId == car.Id).Count()

    заменил, но на время работы не повлияло... м.б. 1 секунда разницы... при запуске приложения первичный вывод грида (вызов процедуры заполняющей грид) выполняется примерно 10-11 секунд (ранее было 11-12)... при нажатии кнопки обновить - которая выполняет ту же процедуру - её отработка и в этом и в старом случае занимает 3-4 секунды...

    CarsGrid.DataSource = CarsQuery.ToArray();


    В этом случае данные, полученные из запроса, будут сохранены в массиве, и далее берутся из него.

    тоже что и ToList... работать работает, но на скорость не влияет практически... но при обоих методах теряется возможность сортировки (кликом на заголовках столбцов)
    26 февраля 2014 г. 7:01
  • Мне черезвычайно, т.е. катастрофически не нравится ваш запрос. Вот здесь написано как получить SQL который уходит в базу. Попробуйте этот запрос выполнить в SQL студии и посмотрите его время выполнения.

    вот запрос который выводится через dc.Log = Console.Out

    SELECT [t13].[Id] AS [CarId], [t13].[value] AS [PhotosCount], [t13].[RfidNum], [t15].[Name] AS [Brand], [t14].[Name] AS [Model], [t13].[VIN], [t16].[Name] AS [LoactionCar], [t13].[value4] AS [Parking], [t13].[value3] AS [BuySheme], [t13].[Status], [t13].[ReserveDate], [t13].[ReserveManager], 
        (CASE 
            WHEN [t13].[PredoplataRUB] IS NOT NULL THEN CONVERT(Int,[t13].[PredoplataRUB])
            ELSE @p8
         END) AS [PredoplataRUB], [t13].[value5] AS [DaysInStock], [t13].[Year], [t13].[Mileage], [t13].[value6] AS [PriceRUB], [t13].[value2] AS [CallFpkString], [t13].[BodyType], [t17].[Name] AS [Color], [t13].[GearBox], [t13].[EngineType], [t13].[EngineVolume], [t13].[EnginePower], [t13].[Transmission], [t13].[CabinType], [t13].[CabinColor], [t13].[PurchaseManager]
    FROM (
        SELECT [t12].[Id], [t12].[VIN], [t12].[Year], [t12].[Mileage], [t12].[Status], [t12].[BodyType], [t12].[GearBox], [t12].[EngineType], [t12].[EnginePower], [t12].[EngineVolume], [t12].[Transmission], [t12].[CabinType], [t12].[CabinColor], [t12].[PurchaseManager], [t12].[ReserveManager], [t12].[ReserveDate], [t12].[Car_Model], [t12].[Location_Car], [t12].[ConfigColor_Car], [t12].[RfidNum], [t12].[PredoplataRUB], [t12].[value], [t12].[value2], [t12].[value3], [t12].[value4], [t12].[value5], CONVERT(Int,[t12].[PriceRUB]) AS [value6]
        FROM (
            SELECT [t11].[Id], [t11].[VIN], [t11].[Year], [t11].[Mileage], [t11].[PriceRUB], [t11].[Status], [t11].[BodyType], [t11].[GearBox], [t11].[EngineType], [t11].[EnginePower], [t11].[EngineVolume], [t11].[Transmission], [t11].[CabinType], [t11].[CabinColor], [t11].[PurchaseManager], [t11].[ReserveManager], [t11].[ReserveDate], [t11].[Car_Model], [t11].[Location_Car], [t11].[ConfigColor_Car], [t11].[RfidNum], [t11].[PredoplataRUB], [t11].[value], [t11].[value2], [t11].[value3], [t11].[value4], CONVERT(Int,(CONVERT(BigInt,(((CONVERT(BigInt,DATEDIFF(DAY, [t11].[InSaleDate], @p2))) * 86400000) + DATEDIFF(MILLISECOND, DATEADD(DAY, DATEDIFF(DAY, [t11].[InSaleDate], @p2), [t11].[InSaleDate]), @p2)) * 10000)) / 864000000000) AS [value5]
            FROM (
                SELECT [t8].[Id], [t8].[VIN], [t8].[Year], [t8].[Mileage], [t8].[PriceRUB], [t8].[Status], [t8].[BodyType], [t8].[GearBox], [t8].[EngineType], [t8].[EnginePower], [t8].[EngineVolume], [t8].[Transmission], [t8].[CabinType], [t8].[CabinColor], [t8].[PurchaseManager], [t8].[InSaleDate], [t8].[ReserveManager], [t8].[ReserveDate], [t8].[Car_Model], [t8].[Location_Car], [t8].[ConfigColor_Car], [t8].[RfidNum], [t8].[PredoplataRUB], [t8].[value], [t8].[value2], [t8].[value3], (
                    SELECT [t10].[Parking]
                    FROM (
                        SELECT TOP (1) [t9].[Parking]
                        FROM [dbo].[ConfigParkings] AS [t9]
                        WHERE [t9].[Id] = ([t8].[Parking])
                        ) AS [t10]
                    ) AS [value4]
                FROM (
                    SELECT [t5].[Id], [t5].[VIN], [t5].[Year], [t5].[Mileage], [t5].[PriceRUB], [t5].[Status], [t5].[BodyType], [t5].[GearBox], [t5].[EngineType], [t5].[EnginePower], [t5].[EngineVolume], [t5].[Transmission], [t5].[CabinType], [t5].[CabinColor], [t5].[PurchaseManager], [t5].[InSaleDate], [t5].[ReserveManager], [t5].[ReserveDate], [t5].[Car_Model], [t5].[Location_Car], [t5].[ConfigColor_Car], [t5].[RfidNum], [t5].[Parking], [t5].[PredoplataRUB], [t5].[value], [t5].[value2], (
                        SELECT [t7].[BuyScheme]
                        FROM (
                            SELECT TOP (1) [t6].[BuyScheme]
                            FROM [dbo].[PurchaseOrders] AS [t6]
                            WHERE ([t6].[PurchaseOrder_Car]) = [t5].[Id]
                            ) AS [t7]
                        ) AS [value3]
                    FROM (
                        SELECT [t2].[Id], [t2].[VIN], [t2].[Year], [t2].[Mileage], [t2].[PriceRUB], [t2].[Status], [t2].[BodyType], [t2].[GearBox], [t2].[EngineType], [t2].[EnginePower], [t2].[EngineVolume], [t2].[Transmission], [t2].[CabinType], [t2].[CabinColor], [t2].[PurchaseManager], [t2].[InSaleDate], [t2].[ReserveManager], [t2].[ReserveDate], [t2].[Car_Model], [t2].[Location_Car], [t2].[ConfigColor_Car], [t2].[RfidNum], [t2].[Parking], [t2].[PredoplataRUB], [t2].[value], ((CONVERT(NVarChar,(
                            SELECT COUNT(*)
                            FROM [dbo].[Anketas] AS [t3]
                            WHERE ([t3].[Quation] = @p0) AND ([t3].[Answer] = (CONVERT(NVarChar,[t2].[Id])))
                            ))) + @p1) + (CONVERT(NVarChar,(
                            SELECT COUNT(*)
                            FROM [dbo].[FPKs] AS [t4]
                            WHERE [t4].[FPK_Car] = ([t2].[Id])
                            ))) AS [value2]
                        FROM (
                            SELECT [t0].[Id], [t0].[VIN], [t0].[Year], [t0].[Mileage], [t0].[PriceRUB], [t0].[Status], [t0].[BodyType], [t0].[GearBox], [t0].[EngineType], [t0].[EnginePower], [t0].[EngineVolume], [t0].[Transmission], [t0].[CabinType], [t0].[CabinColor], [t0].[PurchaseManager], [t0].[InSaleDate], [t0].[ReserveManager], [t0].[ReserveDate], [t0].[Car_Model], [t0].[Location_Car], [t0].[ConfigColor_Car], [t0].[RfidNum], [t0].[Parking], [t0].[PredoplataRUB], (
                                SELECT COUNT(*)
                                FROM [dbo].[CarPhotos] AS [t1]
                                WHERE [t1].[Car_CarPhoto] = [t0].[Id]
                                ) AS [value]
                            FROM [dbo].[Cars] AS [t0]
                            ) AS [t2]
                        ) AS [t5]
                    ) AS [t8]
                ) AS [t11]
            ) AS [t12]
        ) AS [t13]
    INNER JOIN [dbo].[ConfigModels] AS [t14] ON [t14].[Id] = [t13].[Car_Model]
    INNER JOIN [dbo].[ConfigBrands] AS [t15] ON [t15].[Id] = [t14].[ConfigModel_ConfigBrand]
    INNER JOIN [dbo].[ConfigLocations] AS [t16] ON [t16].[Id] = [t13].[Location_Car]
    LEFT OUTER JOIN [dbo].[ConfigColors] AS [t17] ON [t17].[Id] = [t13].[ConfigColor_Car]
    WHERE ([t13].[Status] = @p3) OR ([t13].[Status] = @p4) OR ([t13].[Status] = @p5) OR ([t13].[Status] = @p6) OR ([t13].[Status] = @p7)
    ORDER BY [t13].[Id]
    -- @p0: Input NVarChar (Size = 13; Prec = 0; Scale = 0) [ID автомобиля]
    -- @p1: Input NVarChar (Size = 1; Prec = 0; Scale = 0) [/]
    -- @p2: Input DateTime (Size = 0; Prec = 0; Scale = 0) [26.02.2014 0:00:00]
    -- @p3: Input NVarChar (Size = 8; Prec = 0; Scale = 0) [Свободен]
    -- @p4: Input NVarChar (Size = 3; Prec = 0; Scale = 0) [ПСО]
    -- @p5: Input NVarChar (Size = 6; Prec = 0; Scale = 0) [Резерв]
    -- @p6: Input NVarChar (Size = 10; Prec = 0; Scale = 0) [Предоплата]
    -- @p7: Input NVarChar (Size = 9; Prec = 0; Scale = 0) [Заморожен]
    -- @p8: Input Int (Size = 0; Prec = 0; Scale = 0) [-1]
    -- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.4926

    26 февраля 2014 г. 7:02
  • И сколько у вас этот запрос выполняется в Management Studio на вашей базе с теми же параметрами, что вы передаете из программы?

    26 февраля 2014 г. 7:13
    Отвечающий
  • По старом запросу:

    Время старта : 26.02.2014 11:09:19
    Время вывода : 26.02.2014 11:09:27

    8 секунд на первичную загрузку...

    Время старта : 26.02.2014 11:11:14
    Время вывода : 26.02.2014 11:11:18

    4 секунды на загрузку по кнопке обновить (процедуры одни и теже)

    По новому запросу (заменил код для фото и поставил ToArray() (или ToList() не важно)):

    Время старта : 26.02.2014 11:14:38
    Время вывода : 26.02.2014 11:14:46

    8 секунд на первичную загрузку...

    Время старта : 26.02.2014 11:16:24
    Время вывода : 26.02.2014 11:16:28

    4 секунды на загрузку по кнопке обновить (процедуры одни и теже)

    Получается что никакого прироста производительности и разницы нет.. кроме того что теряю сортировку на заголовках...

    так что же за нафиг такой? :(

    26 февраля 2014 г. 7:18
  • тот же запрос но отработанный на SQL сервере (managment studio)

    выполняется мгновенно... ну может за секунду...

    SELECT [t13].[Id] AS [CarId], [t13].[value] AS [PhotosCount], [t13].[RfidNum], [t15].[Name] AS [Brand], [t14].[Name] AS [Model], [t13].[VIN], [t16].[Name] AS [LoactionCar], [t13].[value4] AS [Parking], [t13].[value3] AS [BuySheme], [t13].[Status], [t13].[ReserveDate], [t13].[ReserveManager], 
        (CASE 
            WHEN [t13].[PredoplataRUB] IS NOT NULL THEN CONVERT(Int,[t13].[PredoplataRUB])
            ELSE -1
         END) AS [PredoplataRUB], [t13].[value5] AS [DaysInStock], [t13].[Year], [t13].[Mileage], [t13].[value6] AS [PriceRUB], [t13].[value2] AS [CallFpkString], [t13].[BodyType], [t17].[Name] AS [Color], [t13].[GearBox], [t13].[EngineType], [t13].[EngineVolume], [t13].[EnginePower], [t13].[Transmission], [t13].[CabinType], [t13].[CabinColor], [t13].[PurchaseManager]
    FROM (
        SELECT [t12].[Id], [t12].[VIN], [t12].[Year], [t12].[Mileage], [t12].[Status], [t12].[BodyType], [t12].[GearBox], [t12].[EngineType], [t12].[EnginePower], [t12].[EngineVolume], [t12].[Transmission], [t12].[CabinType], [t12].[CabinColor], [t12].[PurchaseManager], [t12].[ReserveManager], [t12].[ReserveDate], [t12].[Car_Model], [t12].[Location_Car], [t12].[ConfigColor_Car], [t12].[RfidNum], [t12].[PredoplataRUB], [t12].[value], [t12].[value2], [t12].[value3], [t12].[value4], [t12].[value5], CONVERT(Int,[t12].[PriceRUB]) AS [value6]
        FROM (
            SELECT [t11].[Id], [t11].[VIN], [t11].[Year], [t11].[Mileage], [t11].[PriceRUB], [t11].[Status], [t11].[BodyType], [t11].[GearBox], [t11].[EngineType], [t11].[EnginePower], [t11].[EngineVolume], [t11].[Transmission], [t11].[CabinType], [t11].[CabinColor], [t11].[PurchaseManager], [t11].[ReserveManager], [t11].[ReserveDate], [t11].[Car_Model], [t11].[Location_Car], [t11].[ConfigColor_Car], [t11].[RfidNum], [t11].[PredoplataRUB], [t11].[value], [t11].[value2], [t11].[value3], [t11].[value4], CONVERT(Int,(CONVERT(BigInt,(((CONVERT(BigInt,DATEDIFF(DAY, [t11].[InSaleDate], '2014-02-26 00:00:00.000'))) * 86400000) + DATEDIFF(MILLISECOND, DATEADD(DAY, DATEDIFF(DAY, [t11].[InSaleDate], '2014-02-26 00:00:00.000'), [t11].[InSaleDate]), '2014-02-26 00:00:00.000')) * 10000)) / 864000000000) AS [value5] 
            FROM (
                SELECT [t8].[Id], [t8].[VIN], [t8].[Year], [t8].[Mileage], [t8].[PriceRUB], [t8].[Status], [t8].[BodyType], [t8].[GearBox], [t8].[EngineType], [t8].[EnginePower], [t8].[EngineVolume], [t8].[Transmission], [t8].[CabinType], [t8].[CabinColor], [t8].[PurchaseManager], [t8].[InSaleDate], [t8].[ReserveManager], [t8].[ReserveDate], [t8].[Car_Model], [t8].[Location_Car], [t8].[ConfigColor_Car], [t8].[RfidNum], [t8].[PredoplataRUB], [t8].[value], [t8].[value2], [t8].[value3], (
                    SELECT [t10].[Parking]
                    FROM (
                        SELECT TOP (1) [t9].[Parking]
                        FROM [K20App].[dbo].[ConfigParkings] AS [t9]
                        WHERE [t9].[Id] = ([t8].[Parking])
                        ) AS [t10]
                    ) AS [value4]
                FROM (
                    SELECT [t5].[Id], [t5].[VIN], [t5].[Year], [t5].[Mileage], [t5].[PriceRUB], [t5].[Status], [t5].[BodyType], [t5].[GearBox], [t5].[EngineType], [t5].[EnginePower], [t5].[EngineVolume], [t5].[Transmission], [t5].[CabinType], [t5].[CabinColor], [t5].[PurchaseManager], [t5].[InSaleDate], [t5].[ReserveManager], [t5].[ReserveDate], [t5].[Car_Model], [t5].[Location_Car], [t5].[ConfigColor_Car], [t5].[RfidNum], [t5].[Parking], [t5].[PredoplataRUB], [t5].[value], [t5].[value2], (
                        SELECT [t7].[BuyScheme]
                        FROM (
                            SELECT TOP (1) [t6].[BuyScheme]
                            FROM [K20App].[dbo].[PurchaseOrders] AS [t6]
                            WHERE ([t6].[PurchaseOrder_Car]) = [t5].[Id]
                            ) AS [t7]
                        ) AS [value3]
                    FROM (
                        SELECT [t2].[Id], [t2].[VIN], [t2].[Year], [t2].[Mileage], [t2].[PriceRUB], [t2].[Status], [t2].[BodyType], [t2].[GearBox], [t2].[EngineType], [t2].[EnginePower], [t2].[EngineVolume], [t2].[Transmission], [t2].[CabinType], [t2].[CabinColor], [t2].[PurchaseManager], [t2].[InSaleDate], [t2].[ReserveManager], [t2].[ReserveDate], [t2].[Car_Model], [t2].[Location_Car], [t2].[ConfigColor_Car], [t2].[RfidNum], [t2].[Parking], [t2].[PredoplataRUB], [t2].[value], ((CONVERT(NVarChar,(
                            SELECT COUNT(*)
                            FROM [K20App].[dbo].[Anketas] AS [t3]
                            WHERE ([t3].[Quation] = 'ID автомобиля') AND ([t3].[Answer] = (CONVERT(NVarChar,[t2].[Id])))
                            ))) + '/') + (CONVERT(NVarChar,(
                            SELECT COUNT(*)
                            FROM [K20App].[dbo].[FPKs] AS [t4]
                            WHERE [t4].[FPK_Car] = ([t2].[Id])
                            ))) AS [value2]
                        FROM (
                            SELECT [t0].[Id], [t0].[VIN], [t0].[Year], [t0].[Mileage], [t0].[PriceRUB], [t0].[Status], [t0].[BodyType], [t0].[GearBox], [t0].[EngineType], [t0].[EnginePower], [t0].[EngineVolume], [t0].[Transmission], [t0].[CabinType], [t0].[CabinColor], [t0].[PurchaseManager], [t0].[InSaleDate], [t0].[ReserveManager], [t0].[ReserveDate], [t0].[Car_Model], [t0].[Location_Car], [t0].[ConfigColor_Car], [t0].[RfidNum], [t0].[Parking], [t0].[PredoplataRUB], (
                                SELECT COUNT(*)
                                FROM [K20App].[dbo].[CarPhotos] AS [t1]
                                WHERE [t1].[Car_CarPhoto] = [t0].[Id]
                                ) AS [value]
                            FROM [K20App].[dbo].[Cars] AS [t0]
                            ) AS [t2]
                        ) AS [t5]
                    ) AS [t8]
                ) AS [t11]
            ) AS [t12]
        ) AS [t13]
    INNER JOIN [K20App].[dbo].[ConfigModels] AS [t14] ON [t14].[Id] = [t13].[Car_Model]
    INNER JOIN [K20App].[dbo].[ConfigBrands] AS [t15] ON [t15].[Id] = [t14].[ConfigModel_ConfigBrand]
    INNER JOIN [K20App].[dbo].[ConfigLocations] AS [t16] ON [t16].[Id] = [t13].[Location_Car]
    LEFT OUTER JOIN [K20App].[dbo].[ConfigColors] AS [t17] ON [t17].[Id] = [t13].[ConfigColor_Car]
    WHERE ([t13].[Status] = 'Свободен') OR ([t13].[Status] = 'ПСО') OR ([t13].[Status] = 'Резерв') OR ([t13].[Status] = 'Предоплата') OR ([t13].[Status] = 'Заморожен')
    ORDER BY [t13].[Id]
    

    разница в выводе? я не так работаю с DataGridView напрашивается?

    26 февраля 2014 г. 7:30
  • кое что мне помогло...

                    CarsGrid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
                    CarsGrid.DataSource = CarsQuery;//.ToArray();//.ToList();
                    CarsGrid.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCellsExceptHeader);

    дало увеличение скорости (читай уменьшение времени вывода на экран) на 2 секунды...

    Время старта : 27.02.2014 17:16:05
    Время вывода : 27.02.2014 17:16:07

    куда бы мне ещё капнуть?...

    27 февраля 2014 г. 13:18
  • Можете выложить свой проект (можете убрать оттуда всё, не относящееся к обсуждаемому вопросу)? А также базу данных (можно sql-скрипт, создающий её и наполняющий тестовыми данными).
    27 февраля 2014 г. 14:11
  • к сожалению выложить не могу, но на данный момент свой вопрос пока считаю решенным...

    работа с CarsGrid.AutoSize********* дало существенное уменьшение скорости вывода на экран...

    возможно есть конечно ещё какие-то вещи которые можно было учесть... тогда жду предложений...

    проблема я так понимаю была в том что при выводе каждой строчки на экран для всего грида производился перерасчёт ширины столбцов...

    тогда вопрос... есть ли ещё что-то меняющее своё свойство при выводе новой строки?... я убрал для строк и столбцов... и автосайз делаю уже после вывода для столбцов...

    27 февраля 2014 г. 14:23