вторник, 27 ноября 2012 г.

'ascii' codec can't decode byte 0xe0 in position 0: ordinal not in range(128)

Нередко появлялась ошибка при использовании tortoiseHg: 'ascii' codec can't decode byte 0xe0 in position 0: ordinal not in range(128).

Найденное на просторах интернет решение предлагает редактировать MIME-типы в реестре
  • [HKEY_CLASSES_ROOT\CLSID{4063BE15-3B08-470D-A0D5-B37161CFFD69}\EnableFullPage\MIME]
  • [HKEY_CLASSES_ROOT\MIME\Database\Content Type]
все, что написано с помощью кириллицы либо удаляем либо переименовываем так, чтобы использовались только латинские символы.
Мне помогло. Интересно, что за люди находят решение таких проблем?

четверг, 6 сентября 2012 г.

Установка Mercurial на Ubuntu

В нашей компании в качестве системы управления версиями используется Svn. После того, как наши менеджеры внедрили Agile, пришлось изменить схему работы с Svn. В Svn появилось очень много бранчей жить с которыми не очень приятно. Скажу прямо, иногда, это один большой и страшный пиздец.
Люди говорят, что выход есть - это git. Кроме git'а нашлись ещё mercurial и bazaar. Git мне показался более приспособленным для гиков, которым нужен контроль над вселенной, код в блокноте и командная строка. Mercurial же показался значительно человечнее. Bazaar я ещё не смотрел.
Если изучать возможности и способы работы новой системы контроля версий с точки зрения последующего внедрения в организации, то вопрос развертывания системы приобретает не меньшую актуальность, чем вопрос удобства интерфейса TortoiseHg.

У меня получилось установить Mercurial на Windows 7 и на Ubuntu. На Windows результат работы оказался с косяками, которые мне не удалось победить с наскока, поэтому по крайней мере сейчас описывать эту установку я не буду. Цель развертывания - получить публикуемые в веб репозитории с авторизированным доступом.

64-битная Ubuntu Desktop 12.04 поселилась в VirutalBox в качестве гостевой ОС на компьютере с установленной Windows 7 Ultimate.

Запускаем установку apache2:
Запускаем установку Mercurial:
sudo apt-get install mercurial
Теперь устанавливаем глобальные настройки для Mercurial:
sudo gedit /etc/mercurial/hgrc
В файле hgrc должно содержаться следующее:
[web]
allow_push = *
push_ssl = false
encoding = windows-1251
contact = andrej
[trusted]
users = www-data
В этом файле мы разрешаем push в репозиторий Mercurial'а из сети, перестаем требовать защищенного https подключения для push'ей, устанавливаем кодировку сообщений в windows-1251 для понятного отображения кириллических комментариев оставляемых в Windows. Также мы делаем доверенными пользователей wwws-data, т.е. пользователей apache.
Репозитории будут размещаться в папке /srv/hg/:
sudo mkdir /srv/hg
sudo mkdir /srv/hg/cgi-bin
sudo mkdir /srv/hg/repos
Файл hgweb.cgi распространяется вместе с инсталляцией Mercurial'а - скопируем его в нашу папку cgi-bin и разрешим ему исполняться:
cd /srv/hg/cgi-bin
sudo cp /usr/share/doc/mercurial/examples/hgweb.cgi .
sudo chmod a+x hgweb.cgi
В файле hgweb.cgi нужно указать путь к файлу hgweb.config:
# Path to repo or hgweb config to serve (see 'hg help hgweb')
config = "hgweb.config"
Файл hgweb.config имеет следующее содержание:
[paths]
/ = /srv/hg/repos/*
В нем мы указываем, где размещаются наши репозитории.
Право собственника на папку /srv/hg закрепим за пользователем www-data из под которого запускается сервер apache:
cd /srv
sudo chown -R root:www-data hg
Теперь мы создадим конфигурационный файл виртуальной директории которую apache будет запускать при запросах:
sudo mkdir /etc/apache2/sites-available/hg
sudo gedit /etc/apache2/sites-available/hg/default
Укажем в файле следующее:
NameVirtualHost *
<VirutalHost *>
  ServerAdmin webmaster@localhost

  DocumentRoot /srv/hg/cgi-bin/
  
  ScriptAlias /hg "/srv/hg/cgi-bin/hgweb.cgi"

  <Directory "/srv/hg/cgi-bin/">
    SetHandler cgi-script
    AllowOverride none
    Options +ExecCGI +SymLinksIfOwnerMatch
    Order allow,deny
    allow from all
    AuthType Basic
    AuthName "Mercurial repositories"
    AuthUserFile /srv/hg/cgi-bin/hgusers
    Require valid-user
  </Directory>
 
  ErrorLog /var/log/apache2/hg.log
</VirutalHost>
Отключим прежний сайт и включим новый:
sudo a2dissite default
sudo a2ensite hg
sudo /etc/init.d/apache2 restart

Теперь почти всё готово.
Создадим и добавим в файл hgusers двух пользователей используя утилиту htpasswd:
cd /srv/hg/cgi-bin
sudo htpasswd -mc hgusers hgadmin
sudo chown root:www-data hgusers
sudo htpasswd -m hgusers anderchan
Создадим репозитория hgRepo и hgRepo2
cd /srv/hg/repos
sudo hg init hgRepo
sudo hg init hgRepo2
#сразу добавим файлы hgrc
sudo touch /hgRepo/.hg/hgrc
sudo touch /hgRepo2/.hg/hgrc
#и сделаем новые папки известными apache и позволим ему писать в них
sudo chown -R root:www-data hgRepo
sudo chown -R root:www-data hgRepo2
sudo chmod -R g+r+w hgRepo
sudo chmod -R g+r+w hgRepo2
Для того, чтобы органичить права на запись (операция push) или на чтение мы будем использовать файл hgrc. Пусть в hgRepo может push'ить и просматривать только anderchan. Для hgRepo2 оставим глобальную настройку (записывать может любой, смотреть, также, любой).
sudo nano hgRepo/.hg/hgrc
#печатем и сохраняем:
[web]
allow_push = anderchan
deny_read = anderchan

Если в браузере мы заходим под именем anderchan, то видим два репозитория, если заходим под именем hgadmin, то видим только один репозиторий.



среда, 5 октября 2011 г.

SqlServer 2008: Table Value Parameters & NHibernate

В SqlServer 2008 появилась возможность передавать с клиента на сервер списки объектов.

Тип объектов из которых состоит передаваемая на сервер коллекция должен быть описан в SqlServer. Для списка целых чисел тип определяем следующим образом:

CREATE TYPE [dbo].[IntTable] AS TABLE(
  [n] [int] NOT NULL, 
  PRIMARY KEY CLUSTERED ([n] ASC) WITH (IGNORE_DUP_KEY = OFF)
)

Созданный тип можно посмотреть в обозревателе объектов Managment Studio по пути:
<База данных>/Programmability/Types/User-Defined Table Types.

В программе объявляется отображение типа из .net на sql тип данных. В интерфейсе IType, предназначенном для создания специальных типов данных, все свойства и методы описаны с использованием оборота "если реализовано, то...". Таким образом мы можем реализовать только то, что нам нужно. В примере, IntUserType используется только для передачи в качестве параметра в хранимую процедуру.



public class IntUserType: IType
{
  private static readonly SqlType[] sqlTypes = new[] { new SqlType(DbType.Object) };
  
  public SqlType[] SqlTypes(IMapping mapping)
  {
    return sqlTypes;
  }

  public int GetColumnSpan(IMapping mapping)
  {
    return 1;
  }

  public bool IsCollectionType
  {
    get { return true; }
  }
  
  public void NullSafeSet(IDbCommand st, object value, int index, 
    ISessionImplementor session)
  {
    var s = st as SqlCommand;
    if (s != null)
    {
      s.Parameters[index].SqlDbType = SqlDbType.Structured;
      s.Parameters[index].TypeName = "IntTable";
      s.Parameters[index].Value = value;
    } else
    {
      throw new NotImplementedException();
    }
  }

Интерфейс IType определяет отображение типа данных из .net на тип данных sql.
SqlType[] SqlTypes - возвращает sql-типы столбцов в набор которых будет сохранён данные пользовательский тип.
GetColumnSpan - определяет, сколько столбцов используется для сохранения этого типа данных.
IsCollectionType - устанавливает признак, что тип данных используется в коллекции.
NullSafeSet - сохранение значения в IDbComand.

Далее удобно обзавестись расширением для IQuery, которое позволит привести способ установки в запрос значения параметра с объявленным пользовательским типом данных  к виду, которых используется для стандартных типов данных.


public static class QueryExtension
{
  private static readonly IntUserType intUserType = new IntUserType();
  
  public static IQuery SetIntUserType(this IQuery query, string name,
    DataTable dataTable)
  {
    return query.SetParameter(name, dataTable, intUserType);
  }
}

На сервере БД находится хранимая процедура

CREATE PROCEDURE [dbo].[ListInTest]
 @inParameter IntTable READONLY
AS
BEGIN
 SELECT * from SomeTable where SomeIntField in (select n from @inParameter);
END

которая на клиенте используется следующим простым способом.
List numbers = new List() { 59790543, 55072938, 57469231 };
   
DataTable dataTable = new DataTable();
dataTable.Columns.Add("n", typeof(int));
dataTable.BeginLoadData();
for (int i = 0; i < numbers.Count; i++)
{
 dataTable.Rows.Add(new object[] { numbers[i] });
}
dataTable.EndLoadData();
using (ISession session = NhManager.Instance.SessionFactory.OpenSession())
{
 var query = session.CreateSQLQuery("EXEC ListInTest @inParameter = :id")
  .SetStructured("id", dataTable)
  .SetResultTransformer(Transformers.AliasToBean());
 var vcList = query.List();
}

Раньше коллекции на сервер передавались строками или xml-ками и парсились в sql-скрипте.

Ссылки:
1. Arrays and Lists in SQL Server 2008 (Using Table-Valued Parameters)
2. StackOverflow: NHibernate + SqlDbType.Structured
3. Table-Valued Parameters in SQL Server 2008 (ADO.NET)
4. IType interface

воскресенье, 10 июля 2011 г.

Хранение настроек в Sharepoint 2010

Разработчики SharePoint 2010 имеют множество возможностей для хранения настроек своих приложений [3]. Для хранения настроек в корпоративных проектах, я выбрал механизм PropertyBag. Его и рассмотрим далее в этой статье.

PropertyBag позволяют хранить настройки на уровнях от фермы до списка, что является большим преимуществом данного хранилища. Для того, чтобы правильно пользоваться данным преимуществом, нужно иметь хорошее представление об иерархии объектов SharePoint [1]. Часть иерархии, интересная нам в контексте рассматирваемой проблемы, приведена на следующей схеме.



Каждое WebApplication - это IIS сайт, который может запускаться под уникальным пулом приложений, и который будет содержать Site Collections. С WebApplication таким образом ассоциированны: web.config, конкретный url-адрес и метод аутентификации, а также своя база данных содержимого. Таким образом WebApplication является уровнем на котором выполняется архивирование портала.

Site Collection - это контейнер верхнего уровня, в API SharePoint он представлен классом SPSite. В рамках коллекций сайтов задаются свои правила безопасности (между двумя коллекциями сайтов невозможно никакого наследования прав).

Site (или Web) - это следующий после Site Collection контейнер, в API SharePoint он представлен классом SPWeb.

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

Примеры кода


Далее представлены примеры кода для добавления/чтения/изменения и удаления свойств на некоторых разных уровнях хранения [6].

Добавление свойства на уровне WebApplication.
static void Main(string[] args)
{
SPWebApplication myWebApplication = SPWebApplication.Lookup(new Uri("http://WebApplicationURL"));
myWebApplication.Properties.Add("SPWebAppKey", "SPWebAppValue");
myWebApplication.Update();
Console.WriteLine("Press any key to continue.....");
Console.ReadLine();
}

Просмотр свойства на уровне WebApplication.
static void Main(string[] args)
{
SPWebApplication myWebApplication = SPWebApplication.Lookup(new Uri("http://WebApplicationURL"));
if (myWebApplication.Properties != null && myWebApplication.Properties.Count > 0)
{
if (myWebApplication.Properties.ContainsKey("SPWebAppKey"))
{
Console.WriteLine(myWebApplication.Properties["SPWebAppKey"]);
}
}
Console.WriteLine("Press any key to continue.....");
Console.ReadLine();
}

Изменение свойства на уровне WebApplication.
static void Main(string[] args)
{
SPWebApplication myWebApplication = SPWebApplication.Lookup(new Uri("http://WebApplicationURL"));
myWebApplication.Properties["SPWebAppKey"] = "NewSPWebAppValue";
myWebApplication.Update();
Console.WriteLine("Press any key to continue.....");
Console.ReadLine();
}

Удаление свойства на уровне WebApplication.
static void Main(string[] args)
{
SPWebApplication myWebApplication = SPWebApplication.Lookup(new Uri("http://WebApplicationURL"));
myWebApplication.Properties["WebAppKey"] = null;
myWebApplication.Properties.Remove("WebAppKey");
myWebApplication.Update();
Console.WriteLine("Press any key to continue.....");
Console.ReadLine();
}

Добавление/просмотр свойства на уровне коллекции сайтов (непосредственно сохранять значения в SPSite возможности нет, мы выбираем RootWeb коллекции для хранения настроек).
static void Main(string[] args)
{
SPSite siteCollection = new SPSite("http://servername:port");
SPWeb site = siteCollection.RootWeb;
//Addning SPSite Property
site.Properties.Add("SPSiteKey", "SPSiteValue");
site.Properties.Update();

//Reading SPSite Property
Console.WriteLine(site.Properties["SPSiteKey"]);
Console.WriteLine("Press any key to continue.....");
Console.ReadLine();
}

Добавление/просмотр свойства на уровне сайта:
static void Main(string[] args)
{
SPSite mySite = new SPSite("http://servername:port");
SPWeb myWeb = mySite.OpenWeb("Your Web Name.....");
//Adding SPWeb Property
myWeb.Properties.Add("SPWebKey", "SPWebValue");
myWeb.Properties.Update();

//Reading SPWeb Property
Console.WriteLine(myWeb.Properties["SPWebKey"]);
Console.WriteLine("Press any key to continue.....");
Console.ReadLine();
}

На codeplex есть проект веб-части для изменения/просмотра настроек сохраняемых с помощью PropertyBag[7].

Ссылки


1. SharePoint Object Hierarchy: How it all fits together
2. Creating Your First Web Application, Site Collection and Web Site (video)
3. Managing Application Configuration (msdn)
4. http://social.technet.microsoft.com/Forums/en-US/sharepointadmin/thread/9bef1b5f-d21c-48d8-988d-da370e9ce54b/
5. SharePoint 2010: Farm, web application, sites collection, sites and subsites, how to know what to use?
6. Understanding SharePoint Property Bag Settings
7. SharePoint Property Bag Settings 2010

пятница, 16 января 2009 г.

LINQ to SQL и пространственные данные SQL Server



Начиная с версии 2008 (и пока что заканчивая ей) MS SQL Server имеет встроенную поддержку пространственных данных. Прекрасно!

На данный момент времени уже существует несколько СУБД, предлагающих индексированное хранение пространственных данных. Наверное, самые популярные из них, это: «народная» MySql и PostGIS.

Программируя на c#, естественно, в очень многих случаях, отдаёшь предпочтение продуктам и решениям Microsoft. Причины просты: полнее поддержка одних технологий другими, хорошая документация, более полная реализация, например провайдеров данных, и гораздо меньшая глючность. Я выбрал SQL Server. Заодно захотелось освоить LINQ в общем и LINQ to SQL в. частности.

Поначалу всё было хорошо. Для меня хороший старт сделала, обнаруженная на msdn, статья «LINQ to SQL: .NET Language-Integrated Query for Relational Data».
Но я не сильно удивился, когда «всё хорошо» закончилось.

Для хранения геометрических данных в SQL Server были введены два дополнительных типа: geometry и geography. Первый используется для хранения геометрических объектов, описанных в декартовой системе координат, а второй — для геометрических объектов заданных географическими координатами (широта/долгота).

Такое разделение, по всей видимости, пришлось сделать из-за того, что пространственный индекс реализован в SQL Server на основе B-деревьев. При использовании этого индекса пространство шаблонно разбивается сеткой несколько раз и в «ячейки» этой сетки сохраняются ссылки на геометрические объекты. И оказалось невозможно строить универсальное разбиение и для прямоугольной системы координат и для эллипсоидальной. В MySql, например, выбран другой алгоритм индексирования, основанный на R-деревьях, работающий на совершенно другом принципе, и используется один тип данных. Какой способ индексирования лучше, а какой хуже —
не очевидно, так что пока не понятно на кого ругаться и стоит ли.
Оказалось, что LINQ to SQL не понимает этих типов данных и работать с ними, а также со встроенными геометрическими функциями, отказывается. Хотя, наверное, правильнее сказать, что их не понимает провайдер. В любом случае, уверен, что поддерживаться эти данные будут, но сейчас такой поддержки нет.

Я не смог найти в интернет решения, обходящего эту проблему, поэтому пришлось изобрести его самому. Здесь нет никаких удивительных ходов, но есть детали, которые, думаю, будут интересны. Также в этой большой заметке, для вашего интереса, я чуть-чуть опишу работу с LINQ to SQL.

База данных

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

  1. USE ExampleDatabase;
  2. GO

  3. --Create table
  4. CREATE TABLE Boundaries_Country(
  5. FeatureID UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
  6. CountryName VARCHAR(100) NOT NULL UNIQUE,
  7. CountryBoundary GEOGRAPHY NOT NULL
  8. )

  9. CREATE SPATIAL INDEX SpatialIndex
  10. ON Boundaries_Country (CountryBoundary);
  11. GO

В строке 11 для поля CountryBoundary c типом данных geography, создаётся пространственный индекс с настройками по умолчанию.

Чтобы было с чем работать, я заполнил таблицу мультиполигонами стран мира, shape-файл для которых был найден на просторах интернет. Несколько стран не захотели конвертироваться — я не стал разбираться почему, было не важно, хотя за Россию, безусловно, обидно.

SQL Server имеет симпатичный встроенный просмотрщик.


Начало работы с LINQ to SQL

Для работы с LINQ to SQL в проект нужно добавить ссылки на две сборки: System.Data.Linq и Microsoft.SqlServer.Types. Если с первой библиотекой проблем нет (её можно найти на вкладке «.NET» формы «Add Reference» — добавления ссылки на используемую в проекте библиотеку), то вторую нужно будет поискать в директории «C:\Program Files\Microsoft SQL Server\100\SDK\Assemblies\». Для того, чтобы последняя сборка впредь отображалась во вкладке «.NET» формы добавления сборок, нужно её зарегистрировать один раз с помощью утилиты gacutil.

Первый шаг в использовании LINQ to SQL — это создание классов-отображений для таблиц базы данных.

На одну таблицу — один класс.

  1. using System;
  2. using System.Data.Linq.Mapping;
  3. using Microsoft.SqlServer.Types;

  4. namespace MyNamespace
  5. {
  6. [Table()]
  7. public sealed class Boundaries_Country
  8. {
  9. [Column(AutoSync = AutoSync.OnInsert, DbType = "uniqueidentifier", IsPrimaryKey = true, IsDbGenerated = true, UpdateCheck = UpdateCheck.Never)]
  10. public Guid FeatureID;

  11. [Column(DbType = "varchar(100)", CanBeNull = false)]
  12. public string CountryName;

  13. [Column(/*DbType = "geography", */CanBeNull = false)]
  14. public SqlGeography CountryBoundary;
  15. }
  16. }
Над объявлением класса и над полями расставлены атрибуты. Например, в строке 7, атрибут Table указывает, что этот класс ассоциирован с таблицей в базе данных. Если имя класса совпадает с именем таблицы, то атрибут можно записывать так, как у меня, а если нет, то нужно будет указать дополнительное свойство Name: [Table(Name = "Boundaries_Country")].

В строке 16, при описании атрибута для поля, содержащего пространственные данные, по идее, я должен указать тип данных geography, но поскольку поддержки этого типа данных ещё нет, то я его и не указываю.

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

  1. using System.Data.Linq;

  2. namespace MyNamespace
  3. {
  4. public class ExampleDatabase: DataContext
  5. {
  6. public Table<Boundaries_Country> BoundariesCountry;

  7. public ExampleDatabase(string connectionString)
  8. : base(connectionString)
  9. {

  10. }
  11. }
  12. }
Пример, выгребем из базы данных всё, что есть, но так, чтобы название страны начиналось с буквы «С».
  1. static void Main(string[] args)
  2. {
  3. ExampleDatabase db = new ExampleDatabase(@"...");

  4. var q = from item in db.BoundariesCountry
  5. where item.CountryName.StartsWith("C")
  6. select item;
  7. foreach (var item in q)
  8. Console.WriteLine(item.CountryName);
  9. }
Получилось не так уж и много.

Один интересный момент. Если в режиме отладки остановить выполнение программы на строке 9 и просмотреть содержание переменной q, то мы увидим сформированный LINQ to SQL запрос.



LINQ to SQL: работа с пространственными данными

Рассмотрим запрос, выбирающий из базы данных страны, попавшие в заданный прямоугольник, и название которых начинается на букву «С».

Прямоугольник задан полигоном (WKT-представление): POLYGON ((40 -28, 40 30, 5 30, 5 -28, 40 -28)).
  1. var q = from item in db.BoundariesCountry
  2. where item.CountryName.StartsWith("C") &&
    item.CountryBoundary.STIntersects(sqlEnvelope).Value
  3. select item;

  4. foreach (var item in q)
  5. Console.WriteLine(item.CountryName);
Этот код скомпилируется, но работать не будет.

Во время выполнения, на строке 5, когда от LINQ to SQL потребуется отправить запрос на сервер, будет выброшено исключение: "Method 'System.Data.SqlTypes.SqlBoolean STIntersects(Microsoft.SqlServer.Types.SqlGeography)' has no supported translation to SQL.".

Чтобы решить эту проблему мы будем использовать хранимые процедуры и table-valued функции, а пересылать геометрические объекты на сервер будем в хорошо понятном SQL Server'у бинарном формате WKB.

Хранимые процедуры

Для выборки геометрических фигур по критерию попадания в заданный прямоугольник для одной конкретной таблицы, достаточно создать следующую простую хранимую процедуру.
  1. CREATE PROCEDURE [dbo].[sp_bbx_Boundaries_Country]
  2. @boundingBox varbinary(max)
  3. AS
  4. BEGIN
  5. SET NOCOUNT ON;
  6. SELECT *
  7. FROM dbo.Boundaries_Country
  8. WHERE GEOGRAPHY::STGeomFromWKB(@boundingBox,
    4326).STIntersects(CountryBoundary) = 1;
  9. RETURN;
  10. END
Входной параметр — это прямоугольник (заданный полигоном) в WKB-формате. В строке 8 он преобразуется статическим методом STGeomFromWKB в объект типа данных geography и уже на нём вызывается функция STIntersects, осуществляющая проверку на попадание конкретной границы в прямоугольник.

В программе, в классе, реализующем DataContext (у нас этот класс называется ExampleDatabase), опишем обёртку для вызова этой процедуры.
  1. [Function()]
  2. public ISingleResult<Boundaries_Country> sp_bbx_Boundaries_Country(
    [Parameter(DbType = "varbinary(max)")] byte[] boundingBox)
  3. {
  4. IExecuteResult execResult = this.ExecuteMethodCall(this, ((MethodInfo)
    (MethodInfo.GetCurrentMethod())), boundingBox);
  5. ISingleResult<Boundaries_Country> result =
    ((ISingleResult<Boundaries_Country>)execResult.ReturnValue);
  6. return result;
  7. }
Здесь, также как и для таблиц, описываются атрибуты для функции и параметров.

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

Пользуемся этой «радостью» следующим образом:
  1. var q = from item in db.sp_bbx_Boundaries_Country(
    sqlEnvelope.STAsBinary().Buffer)
  2. where item.CountryName.StartsWith("C")
  3. select item;

  4. foreach (var item in q)
  5. Console.WriteLine(item.CountryName);
Результат на консоль.
Замечание, если у вас, как у меня в рабочем проекте, несколько таблиц с географическими данными, то имеются следующие варианты.

  1. Для каждой таблицы создать свою хранимую процедуру, а в программе для каждой хранимой процедуры свою функцию-обёртку. Можно упростить жизнь пользователю api, если написать "центральный" generic-метод в котором, по актуальному типу, используемому при вызове generic-метода, будут вызываться приватные метод-обёртки хранимых процедур и выполняться необходимые приведения типов.
  2. Написать одну хранимую процедуру с использованием динамического sql. В программе нужно будет сделать один generic-метод, из которого также, как и в предыдущем варианте будут вызывать специализированные (по типу данных) методы-обёртки вокруг одной и той же процедуры (с наскока уйти от этого не удалось, воевать не стал).


Хранимые процедуры — это хорошо, но при использовании в LINQ to SQL, в описанной манере, у них есть один существенный недостаток: хранимые процедуры исполняются сразу и с сервера на клиент пересылаются все, попавшие в заданный регион, страны и уже потом над этим массивом выполняется дополнительная фильтрация. Т.е. трансляции в SQL всего LINQ-выражения не происходит. Для ухода от этой проблемы мы можем использовать inline-функции SQL Server'а.

Table-valued функции


Table-valued функция, извлекающая из таблицы базы данных записи по критерию попадания в заданный регион, может быть создана следующим образом.

  1. CREATE FUNCTION [dbo].[f_bbx_Boundaries_Country]
  2. (
  3. @boundingBox varbinary(max)
  4. )
  5. RETURNS TABLE
  6. AS
  7. RETURN
  8. (
  9. SELECT *
  10. FROM dbo.Boundaries_Country
  11. WHERE GEOGRAPHY::STGeomFromWKB(
    @boundingBox, 4326).STIntersects(CountryBoundary) = 1
  12. )
Т.е. по содержанию полностью аналогично хранимой процедуре, описанной раньше.

Для функции также нужно будет создать свою обёртку.
  1. [Function(IsComposable = true)]
  2. public IQueryable<Boundaries_Country> f_bbx_Boundaries_Country(
    [Parameter(DbType = "varbinary(max)")] byte[] boundingBox)

  3. {
  4. return this.CreateMethodCallQuery<Boundaries_Country>(this,
    ((MethodInfo)(MethodInfo.GetCurrentMethod())), boundingBox);
  5. }
В атрибуте метода указываем свойство IsComposable, которое говорит, что сейчас мы будем запускать функцию на SQL Server, а не хранимую процедуру. Для вызова функции используется метод CreateMethodCallQuery.

Смотрим пример.
  1. var q = from item in db.f_bbx_Boundaries_Country(
    sqlEnvelope.STAsBinary().Buffer)
  2. where item.CountryName.StartsWith("C")
  3. select item;

  4. foreach (var item in q)
  5. Console.WriteLine(item.CountryName);
Результат тот же, что и при использовании хранимой процедуры.

И в отладке видим прекрасную картину (всё linq-выражение было транслировано в sql-запрос):



суббота, 10 января 2009 г.

The realization of the generic names

GenericName is a sequence of identifiers rooted within the context of a namespace, for geoTools project was derived in restricted form from the ISO 19103. Restrictions were made for the reason of useless some parts of the standard for tasks of the developed library, and also becouse of some elements of classes in the standard are not specificated and have no clear meaning.

I want to offer a little bit more flexible classes for Generic name than I found in geoTools realization. Also, for my realization i've used another program language: c# instead of java.

GenericName is the base class for two descendants: ScopedName and LocalName.

Figure 1. GenericName uml class diagram

The main question is, what all these methods do?

GenericName.ParsedNames — returns a list of LocalNames which constitute this name.
For the LocalName the list contain just one LocalName, concretely, this name.

//LocalName:
public override List<LocalName> ParsedNames
{
  get
  {
    if (parsedNames == null)
      parsedNames = new List<LocalName>(new LocalName[1] { this });
    return parsedNames;
  }
}

//ScopedName:
public override List<LocalName> ParsedNames
{
  get
  {
    if (parsedNames == null)
    {
      parsedNames = new List<LocalName>();
      parsedNames.Add(head);
      parsedNames.AddRange(tail.ParsedNames);
    }
    return parsedNames;
  }
}



GenericName.FullyQualifiedName — returns a view of this name as a fully-qualified name.
In the common this method returns a ScopedName which head lays in the global namespace, for my realization it means, that the namespace property has value null. This method may return a LocalName instance in the case if we call it for LocalName instance with a null namespace.

And the realization for LocalName, and for ScopedName.

//LocalName:
public override GenericName FullyQualifiedName
{
  get
  {
    if (nameSpace != null)
    {
      ScopedName fqn = new ScopedName(this.Head.NameSpace.Tip, this);
      while (fqn.Head.NameSpace != null)
        fqn = new ScopedName(fqn.Head.NameSpace.Tip, fqn);
      return fqn;
    }
    else
      return new LocalName(null, name);
  }
}

//ScopedName:
public override GenericName FullyQualifiedName
{
  get
  {
    ScopedName fqn = this;
    if (this.Head.NameSpace != null)
    {
      fqn = new ScopedName(this.Head.NameSpace.Tip, this);
      while (fqn.Head.NameSpace != null)
        fqn = new ScopedName(fqn.Head.NameSpace.Tip, fqn);
    }
    return fqn;
  }
}



Generic.ToString() — returns a textual representation of this name. The code for LocalName and ScopedName is presented below.

//LocalName:
public override string ToString()
{
  return name;
}

//ScopedName:
public override string ToString()
{
  StringBuilder builder = new StringBuilder();
  for (int i = 0; i < ParsedNames.Count - 1; i++)
  {
    builder.Append(parsedNames[i].ToString());
    builder.Append(GenericName.DELIMETER);
  }
  builder.Append(parsedNames[parsedNames.Count - 1].ToString());
  return builder.ToString();
}



Last the properties and methods are obvious.