понедельник, 27 декабря 2010 г. - www.msmirnov.ru

Dependency Injection и IoC-контейнеры на примере MEF и NInject - Доклад Андрея Маркеева

Несколько дней назад Андрей Маркеев прочитал нам свой замечательный доклад "Dependency Injection и IoC-контейнеры на примере MEF и NInject".

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

Видео доклада доступно здесь:


или по ссылке: http://rutube.ru/tracks/3912859.html?v=e82a93d8e3d62fc22a4209c6f253ff4d
Мой сайт - www.msmirnov.ru

вторник, 21 декабря 2010 г. - www.msmirnov.ru

Влияют ли cross database joins на производительность запросов в SQL Server?

На днях мне пришлось столкнуться с вопросом – как влияют cross database joins на производительность запросов в SQL Server, при условии что базы находятся в пределах одного сервера?

Поискав в Google я не нашел однозначного ответа на этот вопрос – кто-то писал что разницы нет никакой, кто-то наоборот отмечал двукратное замедление запросов.

Поскольку для меня это важно, я провел несколько тестов на выборку и вставку, в которых объединялись две таблицы – одна размеров в 8 миллионов строк и другая в 30 тысяч строк.
Эти таблицы поочередно располагались в одной и в разных базах.

Никакой разницы в значениях logical reads, physical reads, duration не было.

Поэтому мой ответ – Нет, cross database joins не влияют на производительность запросов в SQL Server.

Мой сайт - www.msmirnov.ru

понедельник, 20 декабря 2010 г. - www.msmirnov.ru

Partitioned views на практике. Горизонтальное секционирование (партицирование) через partitioned views в SQL Server. Опыт собственной разработки.

Введение.

Сам по себе прием partitioned views, о котором пойдет речь в данной статье, известен уже около 10-ти лет, но здесь мне бы хотелось поделиться именно практическим опытом его реализации в современной обстановке.

Кратко напомню суть приема partitioned views.

Partitioned views используются для организации горизонтального секционирования больших таблиц баз данных с целью ускорения доступа и повышения производительности запросов.

Приведу пример.

Создадим три одинаковые по структуре таблицы, которые будут хранить некие отчетные данные:

ReportTable_History будет хранить данные до начала 2011 года,
ReportTable_2011_01 за январь 2011 и
ReportTable_2011_02 за февраль 2011.

Для этого выполним следующий SQL-скрипт:

-- Первая таблица

CREATE TABLE ReportTable_History(ReportDate datetime NOT NULL, SalesCount int NOT NULL, SalesAmount money NOT NULL)
GO

CREATE CLUSTERED INDEX CL_INDEX ON ReportTable_History (ReportDate)


ALTER TABLE ReportTable_History WITH CHECK ADD CONSTRAINT CK_ReportTable_History CHECK ((ReportDate<'2011-01-01'))
GO
 
-- Вторая таблица
CREATE TABLE ReportTable_2011_01(ReportDate datetime NOT NULL, SalesCount int NOT NULL, SalesAmount money NOT NULL)
GO

CREATE CLUSTERED INDEX CL_INDEX ON ReportTable_2011_01 (ReportDate)

ALTER TABLE ReportTable_2011_01 WITH CHECK ADD CONSTRAINT CK_ReportTable_2011_01 CHECK ((ReportDate>='2011-01-01' and ReportDate < '2011-02-01'))
GO


-- Третья таблица
CREATE TABLE ReportTable_2011_02( ReportDate datetime NOT NULL, SalesCount int NOT NULL, SalesAmount money NOT NULL)
GO


CREATE CLUSTERED INDEX CL_INDEX ON ReportTable_2011_02 (ReportDate)

ALTER TABLE ReportTable_2011_02 WITH CHECK ADD CONSTRAINT CK_ReportTable_2011_02 CHECK ((ReportDate>='2011-02-01' and ReportDate < '2011-03-01'))
GO


Как видим, все таблицы имеют одинаковую структуру и имеют также ограничения по полю ReportDate, которые не позволяют нам вставлять в эти таблицы данные за “неправильные” даты.


Создадим теперь представление следующего вида, которое будет объединять в себе все три таблицы:

CREATE VIEW ReportView AS
select ReportDate, SalesCount, SalesAmount
from ReportTable_History
union all
select ReportDate, SalesCount, SalesAmount
from ReportTable_2011_01
union all
select ReportDate, SalesCount, SalesAmount
from ReportTable_2011_02


Теперь попробуем выполнить вставку данных за Январь 2011 в ReportTable_2011_01:

insert into ReportTable_2011_01 (ReportDate, SalesCount, SalesAmount)
values ('2011-01-01', 1, 100)

 
 
Проверим содержимое таблицы за Январь 2011 (ReportTable_2011_01):

image

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


Теперь самое интересное – попробуем теперь выбрать январские данные из представления ReportView и посмотрим план запроса:

image

Из плана видим, что сервер производит обращение только к таблице ReportTable_2011_01, в которой содержатся данные за Январь 2011. Другие две таблицы при выполнении запроса не задействованы. Это происходит благодаря нашим CHECK CONSTRAINTS, которые помогают оптимизатору правильно определить месторасположение данных.

Для выбирающего скрипта эта работа не видна, т.е. он не знает про внутреннюю структуру view и про распределение данных по таблицам, вместо этого он просто обращается к view, как к обычной таблице.

Таким образом происходит распределение данных между таблицами, что уменьшает каждую из них и ускоряет работу с таблицами.

Данный прием также работает в случае если таблицы доступны через linked servers, только в таком случае происходит распределение данных не только между локальными таблицами, но и между серверами.

В SQL Server, начиная с версии 2005, существует встроенная возможность разбивать большие таблицы на партиции, но данная функция присутствует только в редакциях Developer и Enterprise.

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

Надеюсь в будущих версиях Microsoft это исправит, но сейчас мы имеем то, что имеем – для использования партиций надо покупать редакцию Enterprise.

Таким образом, встает вопрос собственной реализации механизма partitioned views.



Описание механизма.

Рассмотренный здесь механизм был использован для организации доступа к большим таблицам отчетных данных с секционированием по отчетным периодам (собственно говоря, именно такой пример я и привел во Введении). В описании механизма я буду рассматривать секционирование по месяцам.

На вскидку можно было бы предположить, что мы можем организовать partitioned view, которое будет объединять в себе неограниченное число таблиц, созданное для большого числа месяцев. Например, можно было бы объединить в нем 120 таблиц для отчетных данных за 10 лет.

К сожалению, это не так.

Длина запроса view ограничена 8000 символов, поэтому мы не можем создать partitioned view, достаточно длинное для объединения любого количества таблиц.

Как вариант решения проблемы, можно было бы организовать секционное хранение данных только за последние 12 месяцев, а все более старые данные хранить в одной отдельной общей таблице (как показано во введении).

На первый взгляд это логично, так как данные за последние 12 месяцев являются наиболее часто востребованными, поэтому именно для них является наиболее актуальной задача быстрого доступа. Конечно, 12 месяцев – это частый случай, количество может и больше, лишь бы хватило длины view.

Таким образом, наше partitioned view могло бы содержать одну большую таблицу, которая хранит данные старше 12-ти месяцев и 12 малых таблиц для 12-ти последних месяцев каждая.

На следующем рисунке показано, что partitioned view объединяет 12 таблиц по месяцам и одну большую таблицу.

image

Соответственно, перед началом каждого месяца мы должны были бы переместить 12-й месяц в большую таблицу и добавить в представление новую 1-ю таблицу для нового месяца.

image

Для этого мы должны были бы выполнить следующие действия:
1. Исправить CHECK CONSTRAINTS на большой таблице так, чтобы разрешенная в ней дата увеличилась на 1 месяц (т.е. чтобы таблица опять включала бы в себя все данные старше 12-ти месяцев, с учетом перехода на новый месяц).
2. Перенести данные из таблицы, которая содержала данные 12-ти месячной давности в большую таблицу.
3. Удалить пустую таблицу, в которой содержались данные 12-ти месячной давности.
4. Создать новую таблицу для нового месяца с новым CHECK CONSTRAINTS.
5. Изменить скрипт partitioned view, добавив в него новую таблицу и удалив старую.

Этот механизм будет работать, но он имеет ряд существенных недостатков:
1. Его довольно сложно поддерживать, так как структуры месячных таблиц и большой таблицы разные и мы не можем сформировать универсальный механизм генерации таблиц за любой период.
2. Проблема быстрого доступа к “старым” данным не решена – они все равно располагаются в большой таблице.
3. Необходимо выполнять действия по перемещение данных между таблицами, которые могут быть довольно затратными.

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

В качестве решения всех этих проблем можно использовать двухуровневую организацию таблиц и представлений:
1. Организовать хранение данных только в месячных таблицах: ReportTable_2011_01, ReportTable_2011_02 и т.д.
2. Создать набор представлений по годам, объединяющих по 12 месячных таблиц за каждый год.
3. Создать одно общее представление второго уровня, объединяющее представления по годам.
На схеме это можно показать следующим образом:

image

При это при обращении за получением данных к ReportView оптимизатор по прежнему будет выполнять обращение только к той таблице, которая содержит требуемые данные. Т.е. оптимизатор корректно обрабатывает наличие двух-уровневых partitioned views.
Длины скриптов таких представлений должно хватить на несколько десятков лет, что будет вполне достаточно для большинства прикладных систем.


Реализация механизма.

В процессе работы с таким представлением нам необходимо выполнять следующие операции:
1. Производить работу с данными в таблицах – вставлять, обновлять и удалять записи.
2. Создавать новые таблицы и изменять скрипты представлений, добавляя в них эти новые таблицы.

Сначала рассмотрим работу с данными.

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

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

Этот процесс лучше автоматизировать – т.е. дать нам такой способ выполнения запросов, который позволит нам выполнять запросы не задумываясь о том, в какой конкретно таблице физически расположены наши данные.

Для этого прежде всего изменим структуру таблиц и представлений.

Новые скрипты таблиц будут такими:
-- Таблица на Январь 2011

CREATE TABLE ReportTable_2011_01(ReportDate datetime NOT NULL, SalesCount int NOT NULL, SalesAmount money NOT NULL, [Version] tinyint)
GO

CREATE CLUSTERED INDEX CL_INDEX ON ReportTable_2011_01 (ReportDate)

ALTER TABLE ReportTable_2011_01 WITH CHECK ADD CONSTRAINT CK_ReportTable_2011_01 CHECK ((ReportDate>='2011-01-01' and ReportDate < '2011-02-01'))
GO

-- Таблица на Февраль 2011
CREATE TABLE ReportTable_2011_02( ReportDate datetime NOT NULL, SalesCount int NOT NULL, SalesAmount money NOT NULL, [Version] tinyint) GO

CREATE CLUSTERED INDEX CL_INDEX ON ReportTable_2011_02 (ReportDate) ALTER
TABLE ReportTable_2011_02 WITH CHECK ADD CONSTRAINT CK_ReportTable_2011_02 CHECK ((ReportDate>='2011-02-01' and ReportDate < '2011-03-01'))

GO

Теперь создадим наше представление первого уровня:


CREATE VIEW ReportView_2011
AS
select ReportDate, SalesCount, SalesAmount
from ReportTable_2011_01
union all
select ReportDate, SalesCount, SalesAmount
from ReportTable_2011_02

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

CREATE VIEW ReportView
AS
select ReportDate, SalesCount, SalesAmount
from ReportView_2011


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


Для этого создадим универсальную процедуру, которая будет принимать на входе SQL-запрос и выполнять его на к таблице или таблицам, в которых физически располагаются необходимые данные.

Рассмотрим простой вариант такой процедуры:
CREATE PROCEDURE ExecPartScript
   
@script nvarchar(4000),
    @TableName nvarchar(200),
   
@MinDate datetime AS
begin 

    declare @sql_script nvarchar(4000
    declare @PartTableName varchar(200
    while (@MinDate < getdate()) 
    begin 
        exec GetTableNameAtDate @CheckDate = @MinDate, @TableName = @TableName, @PartTableName = @PartTableName output 
        set @sql_script = replace(@script, @TableName, @PartTableName
        execute dbo.sp_executesql @sql_script 
        set @MinDate = cast((convert(varchar (08), dateadd(month, 1, @MinDate), 21) + '01') as datetime)     end
 end

Данная процедура принимает на вход запрос, который ей необходимо выполнить на наборе таблиц (@script), общая часть имен таблиц для выполнения запроса (@TableName – в нашем примере это ‘ReportTable’)  и минимальная дата секционирования, с которой необходимо выполнить запрос (@MinDate).

Параметр @MinDate, вообще говоря, можно было бы и не использовать, но его применение позволяет сократить время и нагрузку от выполнения процедуры.

Как видим, процедура выполняет цикл по месяцам, которые являются основной для разбивки таблиц.

Для каждого месяца она запускает процедуру GetTableNameAtDate, которая возвращает имя конкретной таблицы для выполнения запроса (т.е. например ‘ReportTable_2011_02’ вместо ‘ReportTable’). Затем в цикле происходит подмена имени таблицы на новое имя и затем происходит выполнение запроса.

Процедура GetTableNameAtDate довольно проста:

CREATE PROCEDURE GetTableNameAtDate
 @CheckDate datetime,
 @TableName nvarchar(200),
 @PartTableName nvarchar(200) output
AS
BEGIN 

    declare @part_name nvarchar(4
    set @part_name = month(@CheckDate
    if (len(@part_name) = 1) set @part_name = '0' + @part_name 
    set @part_name = '_' + cast (year(@CheckDate) as varchar(4)) + '_'  + @part_name
     set @PartTableName = @TableName + @part_name
    if not exists ( select * from dbo.sysobjects where id = object_id(@PartTableName) and OBJECTPROPERTY(id, N'IsUserTable') = 1)

       exec CreateTable @TableName = @TableName, @part_name = @part_name, @CheckDate = @CheckDate
END

Данная процедура подставляет дату к имени таблицы и возвращает новое имя.

Попутно она вызывает процедуру CreateTable, которая создает новую таблицу в базе, если ее еще нет.

CREATE PROCEDURE [dbo].[CreateTable]
 @TableName nvarchar(200),
 @part_name nvarchar(4),
 @CheckDate datetime AS
BEGIN 

    declare @script nvarchar(4000
    exec ScriptTable @TableName, @script output 
    set @script = replace(@script, @TableName, @TableName + @part_name
    declare @pos int 
    set @pos =
    set @pos = charindex('constraint', @script
    while (@pos > 0
        begin 
            set @pos = charindex(']', @script, @pos
            if (@pos = 0
                break
            set @script = stuff(@script, @pos, 0, @part_name
            set @pos = charindex('constraint', @script, @pos + 2
        end 
    declare @date_column varchar (100
    set @date_column = 'ReportDate' 
    declare @startdate varchar (15), @enddate varchar(15
    set @startdate = convert(varchar (05), @CheckDate, 21) + '01-01' 
    set @enddate = convert(varchar (05), dateadd(year, 1, @CheckDate), 21) + '01-01' 
    set @script = @script + 'ALTER TABLE [dbo].[' + @TableName + @part_name + '] WITH CHECK ADD CONSTRAINT [CK_ReportTable' + @TableName + @part_name + '] CHECK (([' + @date_column + ']>=''' + @startdate + ''' and [' + @date_column + ']<''' + @enddate + ''')) ' 
    execute dbo.sp_executesql @script
 END
Данная процедура вызывает процедуру скриптования таблицы ScriptTable, переименовывает в ней ограничения, добавляет ограничение по дате и запускает скрипт генерации таблицы.

Процедура ScriptTable возвращает скрипт таблицы и чтобы не перегружать данную статью информацией, я рассмотрю ее в отдельном посте.
Итак, на выходе мы получаем процедуру ExecPartScript, которую мы можем использовать для выполнения запросов к секционированным таблицам, которая автоматически перенаправляет запросы на различные физические таблицы, а также организует создания новые секционированных таблиц по мере необходимости.
Мой сайт - www.msmirnov.ru

понедельник, 29 ноября 2010 г. - www.msmirnov.ru

Лекция Андрея Маркеева "Анемичная модель и BLToolkit"

Несколько дней назад Андрей Маркеев читал у нас лекцию "Анемичная модель и BLToolkit".
Видео-запись лекции можно посмотреть здесь или по ссылке.
Мой сайт - www.msmirnov.ru

среда, 24 ноября 2010 г. - www.msmirnov.ru

Как посмотреть sql-запрос, выполняющийся в заданном процессе SQL Server’а

Иногда возникает ситуация, когда на SQL Server’е выполняется сложный запрос, который имеет сложную структуру и отнимает много времени.

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

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

Список процессов, выполняющихся на сервере можно посмотреть через представление master.dbo.sysprocesses которое не содержит текста запроса.

Посмотрим, как мы можем получить запрос, который в данный момент выполняется каком-либо из процессов.

Для начала объявим переменную, содержащую идентификатор нашего процесса:

declare @SPID
set @SPID = 82

В представлении sysprocesses нас будут интересовать следующие поля:
-
sql_handle – ссылка на исполняющийся TDS-пакет.
-
stmt_start – смещение начала строки кода, исполняющейся в данный момент в TDS-пакете, в формате unicode.
- stmt_end – смещение конца строки кода, исполняющейся в данный момент в TDS-пакете, в формате unicode.


Объявим переменные для данных значений и прочитаем их из представления:

DECLARE @sql_handle binary(20), @stmt_start int, @stmt_end int

@sql_handle = sql_handle,
@stmt_start
= stmt_start/2,

@stmt_end
= CASE WHEN stmt_end = -1 THEN -1 ELSE stmt_end/2
END
FROM
master.dbo.
sysprocesses

WHERE spid = @SPID AND ecid = 0

Значения stmt_start и stmt_end делим на два, так как в формате unicode для каждого символа расходуется два байта.

Для получения исполняющегося кода используем функцию fn_get_sql, которая возвращает код запроса для заданной ссылки на пакет.

Данная функция возвращает поле [text] и нам необходимо использовать значения переменных @stmt_start и @stmt_end для того, чтобы получить исполняемый код.

Делаем это следующим образом:

DECLARE @line nvarchar(4000)

SET @line = (SELECT SUBSTRING([text], COALESCE(NULLIF(@stmt_start, 0), 1),
  CASE @stmt_end WHEN -1 THEN DATALENGTH([text]) ELSE (@stmt_end - @stmt_start) END) FROM ::fn_get_sql(@sql_handle)) 

print @line



В результате переменная @line содержит именно ту часть кода, которая исполняется в данный момент.


Обобщая все вышесказанное, можно создать хранимую процедуру, которая печатает исполняемый код на идентификатору процесса:


CREATE PROCEDURE PrintCurrentCode
  @SPID int
AS
DECLARE
@sql_handle binary(20), @stmt_start int, @stmt_end
int


SELECT @sql_handle = sql_handle, @stmt_start = stmt_start/2, @stmt_end = CASE WHEN stmt_end = -1 THEN -1 ELSE stmt_end/2 END
FROM
master.dbo.
sysprocesses

WHERE spid = @SPID AND ecid = 0

DECLARE @line nvarchar(4000)

SET @line = (SELECT SUBSTRING([text], COALESCE(NULLIF(@stmt_start, 0), 1),
  CASE @stmt_end WHEN -1 THEN DATALENGTH([text]) ELSE (@stmt_end - @stmt_start) END) FROM ::fn_get_sql(@sql_handle))

print @line
 
Мой сайт - www.msmirnov.ru

вторник, 23 ноября 2010 г. - www.msmirnov.ru

Статус-митинги

Несколько месяцев назад я изменил формат проведения наших статус-митингов.

Статус-митинги я провожу каждую пятницу в 16:30 на протяжении последних нескольких лет (уже не помню скольких, наверное лет 5-ти).

Раньше к каждому статус митингу я готовил небольшой отчет, в котором отмечал над какими задачами мы работали на прошедшей неделе, что у нас получилось, что нет и почему. Кроме того, я готовил план на следующую неделю, в который помещал основные задачи для каждого из членов команды. Данные два документа сохранялись в MS Groove, так что сейчас я могу посмотреть историю всех планов и статус-митингов начиная с 2007 года (до этого Groove не использовался).

Таким образом, статус-митинг представлял собой мой монолог, в котором я подводил итоги прошедшей недели и рассказывал о планах на следующую неделю. Бывали, конечно, и другие вопросы, но это была основа.

Несколько месяцев назад я решил изменить формат проведения статус-митингов – вместо меня теперь каждый из членов команды (включая и меня самого) рассказывает чем он занимался прошедшую неделю, что у него получилось, с какими проблемами ему пришлось столкнуться и так далее.
Тут же, в процессе рассказа, имеется возможность кратко обсудить что-то по сказанному, задать какие-то вопросы и так далее.
Т.е. форма проведения стала дискуссионной.

В завершении статус митинга я по прежнему озвучиваю план на следующую неделю для каждого члена команды.

Конечно, длительность статус-митинга благодаря изменениям увеличилась – раньше он занимал около 5-ти минут, теперь около 30-40-ка минут, в зависимости от ситуации.

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

Мой сайт - www.msmirnov.ru

вторник, 16 ноября 2010 г. - www.msmirnov.ru

Он-лайн конференция «Управление проектами 2011. Работа с Заказчиком проекта» День 1.

Сегодня 16 ноября прошел первый день он-лайн конференции “Управление проектами 2011. Работа с Заказчиком проекта”, которую проводила компания “Богданов и партнеры”.

В конференции принимало участие более 100 человек из разных городов России и других стран.

Первый день был имел заглавие “Методика взаимодействия с Заказчиком проекта, тонкости, практические примеры.” 

Первоначально был проведен опрос, “Как давно вы обучались проектному управлению?”
Результаты опроса показались мне интересными:
image

Из интересных докладов, которые удалось послушать, хотелось бы отметить следующие:
1. Владимир Абрамов с докладом “Для чего заказчику разбираться в методологии управления проектами.
Владимир нарисовал собирательный образ наиболее “вредного” заказчика, в котором сосредоточил основные источники проблем в поведении заказчика.
Затем были рассмотрены три основные стратегии поведения заказчика – “Мешать”, “Не мешать” и “Помогать” и их основные характеристики и результаты.
В целом, Владимир показал важность привлечения заказчика к работе над проектом на протяжении всего жизненного цикла, что, в общем-то, давно постулируется в проектом управлении.

Однако, не смотря на все, большинство участников конференции (и я в том числе) посчитало, что заказчики не захотят изучать управление проектами:
image

2.Евгений ПикулевАсимметрия управления проектом. Как пойти одной дорогой с Заказчиком в трудном проекте.
В начале была приведена статистика успешности завершения проектов:
image

На диаграмме видно увеличение количества успешных проектов.

Затем Евгений выделил основные причины возникновения проблем при работе с заказчиком:
image

Далее – основные критерии успешности проекта, с индексом важности каждого из них:
image

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

Завтра будет День 2.

Мой сайт - www.msmirnov.ru

понедельник, 15 ноября 2010 г. - www.msmirnov.ru

Как настроить номер итерации по умолчанию в TFS (Team Foundation Server)

С начала этого месяца мне уже три раза задавали вопрос "Как настроить номер итерации по умолчанию в TFS?".

Ответ на этот вопрос я упонимал здесь, но, поскольку часто спрашивают, решил ответить еще отдельным постом.

Ответ: Никак. Но есть обходной вариант.

Номер итерации хранится в поле Iteration Path, а TFS не позволяет для поля Iteration Path создавать правило DEFAULT, так что значение по умолчанию задавать нельзя. Кроме того, правило PROHIBITEDVALUES задавать также нельзя, поэтому нельзя запретить указывать старые итерации, которые уже остались в прошлом.

Для того, чтобы решить эту проблему я отказался от использования поля Iteration Path и вместо него создал свое аналогичное поле, но только не типа TreePath, а типа String со списком доступных значений, соответствующих списку нужных итераций.

Для этого поля уже было можно задавать значение по умолчанию и вообще любые правила.
Старое поле Iteration Path на форме Work Item'а я заменил на новое и исправил все запросы к списку work item'ов так, чтобы они использовали новое поле вместо старого.

P.S.
Другие мои посты на тему TFS:
1. Миграция в TFS (Team Foundation Server)
2. Кто ошибку создает - тот ее и проверяет?
3. Нумерация версий продукта в TFS (Team Foundation Server)
4. Учет трудозатрат и отчетность в TFS (Team Foundation Server)
5. Как создать work item в TFS (Team Foundation Server) из письма в Outlook
6. Как поместить свой control на форму Work Item в TFS (Team Foundation Server)
7. Как настроить номер итерации по умолчанию в TFS (Team Foundation Server)
Мой сайт - www.msmirnov.ru

воскресенье, 14 ноября 2010 г. - www.msmirnov.ru

Использование кеша планов запросов SQL Server

Доступ к содержимому кеша планов запросов SQL Server можно получить через представление sys.dm_exec_query_stats.

Используя это представление, можно извлечь такую полезную информацию, как:
- время последнего запуска (поле last_execution_time)
- количество запусков (execution_count)
- общее количество процессорного времени, потраченного на выполнение запроса (total_worker_time)
- нагрузка на подсистему ввода-вывода (total_physical_reads, total_logical_reads, total_physical_writes, total_logical_writes)

Роль идентификатора плана запроса выполняет поле plan_handle.

Значение этого поля можно использовать в качестве входного параметра функции sys.dm_exec_sql_text, которая возвращает текст данного запроса.

В качестве примера - запрос, который отображает 10 наиболее часто запускаемых запросов, что может быть полезно при оптимизации работы с базой данных:

select queries.execution_count, q.dbid, q.[text]
from
(select top 10 qs.plan_handle, qs.execution_count
from sys.dm_exec_query_stats qs
order by qs.execution_count desc) as queries
cross apply sys.dm_exec_sql_text(plan_handle) as q
order by queries.execution_count desc
Мой сайт - www.msmirnov.ru