Эндрю Мэттьюс
Оригинал: Using RDF and C# to create an MP3 Manager — Part 2
Я отсутствовал неделю или две, пришлось вкалывать на заключительных стадиях проекта, который мы выпускаем на следующей неделе. Я принимал участие в этом проекте почти 6 месяцев, и на следующей неделе меня ждет заслуженный отдых. Это значит, что у меня накопилось некоторое количество времени для самосовершенствования, которое я намереваюсь потратить на технологии Semantic Web. Пришлось активно убеждать ребят из Readify, что это необходимо параллельно с изучением Silverlight и .NET 3. Я думаю, я убедил их, что консультирование без навыков SW будет затруднительно в ближайшие годы.
Однако хватит об этом, пора вернуться к теме статьи, то есть ко второй части моей мини-серии об использовании технологий Semantic Web при разработке небольшого менеджера файлов MP3.
В конце последней статьи мы создали простой механизм для сериализации объектов в RDF хранилище, который включает ряд сервисов для извлечения релевантной информации из объектов, и связывания ее с предикатами определенными в онтологии. В этой статье я покажу вам противоположную сторону процесса. Нам необходимо иметь возможность делать запросы к хранилищу и получать из него коллекции объектов.
Запрос, который я продемонстрирую, довольно прост, т.к. основная задача этой статьи – десериализация объектов. Как только мы сможем перемещать объекты в обоих направлениях мы сможем сосредоточиться на наращивании возможностей запросов.
В этом примере я просто получу список артистов для пользователей и позволю им выбирать одного артиста из списка. Этот артист затем будет использован в запросе в SemWeb для того, чтобы получить список всех его треков.
Запрос работает как обычно, получаем соединение с хранилищем, создаем запрос, выполняем его и преобразуем результат в объекты:
private IList
DoSearch
(
)
{ MemoryStore ms
= Store.
TripleStore; ObjectDeserialiserQuerySink
sink
=
new ObjectDeserialiserQuerySink
(
);
string qry
= CreateQueryForArtist
(artists
[
0
].
Trim
(
)
); Query query
=
new GraphMatch
(
new N3Reader
(
new StringReader
(qry
)
)
); query.
Run
(ms, sink
);
return tracksFound
= sink.
DeserialisedObjects;
}
В ближайшее время мы рассмотрим ObjectDeserialiserQuerySink. Процесс создания запроса действительно очень прост благодаря простым средствам отражения, которые я создал прошлый раз. Для простоты я использую формат N3 для запроса, но мы можем так же легко воспользоваться и SPARQL. Мы начинаем с префикса для того, чтобы определить пространство имен, с которым мы будем работать. Затем мы перебираем сериализуемые свойства класса Track. Для каждого свойства мы добавляем тройку, которая обозначает следующее: «какой бы Track ни был выбран, выбери также и его свойства». Наконец, мы добавляем имя артиста как известный факт, и тем самым, точно определяем, с какими треками мы будем иметь дело.
private
static
string CreateQueryForArtist
(
string artistName
)
{
string queryFmt
=
"@prefix m: .\n";
foreach
(PropertyInfo info
in OwlClassSupertype.
GetAllPersistentProperties
(
typeof
(Track
)
)
)
{ queryFmt
+=
string.
Format
(
"?track ?{1} .\n", OwlClassSupertype.
GetPropertyUri
(
typeof
(Track
), info.
Name
), info.
Name
);
} queryFmt
+=
string.
Format
(
"?track \"{1}\" .\n", OwlClassSupertype.
GetPropertyUri
(
typeof
(Track
),
"ArtistName"
), artistName
);
return queryFmt;
}
Создав строковое представление запроса, мы передаем его в объект GraphMatch, который представляет собой запрос, в котором вы указываете, какой результат хотите получить, задавая граф как прототип результата. Я также создал простой класс ObjectDeserialiserQuerySink:
public
class ObjectDeserialiserQuerySink
: QueryResultSink where T
: OwlClassSupertype,
new
(
)
{
public List
DeserialisedObjects
{ get
{
return deserialisedObjects;
}
}
private List
deserialisedObjects
=
new List
(
);
public ObjectDeserialiserQuerySink
(
)
{
}
public
override
bool Add
(VariableBindings result
)
{ T t
=
new T
(
);
foreach
(PropertyInfo pi
in OwlClassSupertype.
GetAllPersistentProperties
(
typeof
(T
)
)
)
{
try
{
string vn
= OwlClassSupertype.
GetPropertyUri
(
typeof
(T
), pi.
Name
).
Split
(’
#')[1];
string vVal
= result
[pi.
Name
].
ToString
(
); pi.
SetValue
(t, Convert.
ChangeType
(vVal, pi.
PropertyType
),
null
);
}
catch
(Exception e
)
{ Debug.
WriteLine
(e
);
return
false;
}
} DeserialisedObjects.
Add
(t
);
return
true;
}
}
Для каждого соответствия, которое находит среда, вызывается метод Add десериализатора, которому передается набор объектов связок для переменных (VariableBindings). Каждая такая связка соответствует подстановке для свободных переменных определенных в запросе. Так как мы формировали запрос из сериализуемых свойств класса Track, подстановки для свободных переменных, также будут соответствовать сериализуемым свойствам класса Track. Это означает, что десериализация набора VariableBindings в объект – это очевидный процесс.
Вот и все. Теперь у нас есть простое хранилище RDF, где мы можем хранить наши данные, и простой механизм сериализации для записи и чтения объектов. Но, еще не мало работы нам предстоит. Из всех операций CRUD я реализовал только Create и Retrieve. Осталось доделать Update и Delete. Как вы можете увидеть из одного из моих предыдущих постов, это будет в основном ручная программистская задача т.к. онтологии Semantic Web, до определенной степени, статичны. Это значит, что они моделируют предметную область как некий неизменный набор знаний, при этом мы имеем возможность выводить новые факты, но не можем аннулировать (удалять) существующие знания.
Статическая природа онтологий выглядит, как помеха для тех, кто имеет дело, в основном, с транзакционными данными, т.к. это означает, что мы должны иметь более одного механизма для работы с данными: дедуктивный вывод, и обработка транзакций. В примерах, которые мы рассматривали до сих пор, мы имели дело с хранилищем RDF в памяти, в этом случае только SemWeb API занимался обновлением и удалением данных. Когда мы будем использовать реляционные базы данных в качестве нашего RDF хранилища, мы сможем воспользоваться SQL в качестве альтернативного инструмента для манипулирования данными.