Linq over WCF

IT IT
Как говорится we are proud to announce the very first fully functional Linq provider over WCF. Первый в мире полноценный Linq провайдер работающий через WCF.

Качать как всегда здесь.

Критика, замечания, пожелания, особенно специалистов по WCF и защите, приветствуются.

Запустить пример в одном процессе можно примерно так:

using (var host = new ServiceHost(new LinqService(), new Uri("net.tcp://localhost:1234")))
{
    host.Description.Behaviors.Add(new ServiceMetadataBehavior());
    host.Description.Behaviors.Find<ServiceDebugBehavior>().IncludeExceptionDetailInFaults = true;
    host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexTcpBinding(), "mex");
    host.AddServiceEndpoint(
        typeof(ILinqService),
        new NetTcpBinding(SecurityMode.None)
        {
            MaxReceivedMessageSize = 10000000,
            MaxBufferPoolSize      = 10000000,
            MaxBufferSize          = 10000000,
            CloseTimeout           = new TimeSpan(00, 01, 00),
            OpenTimeout            = new TimeSpan(00, 01, 00),
            ReceiveTimeout         = new TimeSpan(00, 10, 00),
            SendTimeout            = new TimeSpan(00, 10, 00),
        },
        "LinqOverWCF");

    host.Open();

    var ctx = new ServiceModelDataContext(
        new NetTcpBinding(SecurityMode.None)
        {
            MaxReceivedMessageSize = 10000000,
            MaxBufferPoolSize      = 10000000,
            MaxBufferSize          = 10000000,
            CloseTimeout           = new TimeSpan(00, 01, 00),
            OpenTimeout            = new TimeSpan(00, 01, 00),
            ReceiveTimeout         = new TimeSpan(00, 10, 00),
            SendTimeout            = new TimeSpan(00, 10, 00),
        },
        new EndpointAddress("net.tcp://localhost:1234/LinqOverWCF"));

    var list = ctx.GetTable<Person>().ToList();

    host.Close();

    return list;
}


Вкратце как это работает.

BLT Linq провайдер общается с контекстом данных через интерфейс IDataContext, которому передаётся распарсенный Linq запрос в виде структуры SqlQuery. SqlQuery фактически представляет собой SQL AST. ServiceModelDataContext сериализует эту структуру и передаёт LinqService, который в свою очередь формирует SQL, выполняет запрос и возвращает данные клиенту. Всё просто.

Самый больной вопрос в данном подходе, конечно же, безопасность, если она актуальна. LinqService содержит метод ValidateQuery, который можно использовать для валидации запроса. По умолчанию, LinqService всего лишь не позволяет использовать DML операции, но, в принципе, нет никаких проблем сделать более интеллектуальную проверку структуры запроса. Например, на использование несанкционированных таблиц или полей. Собственно говоря, на эту тему хотелось бы побольше поговорить и услышать мнения насчёт типовых сценариев защиты, которые можно было бы реализовать прямо в библиотеке, т.к. хотя SqlQuery и можно анализировать, но в языках не поддерживающих patten matching это занятие не из весёлых.
Andy77
Andy77
09.08.2010 07:57
IT>Как говорится we are proud to announce the very first fully functional Linq provider over WCF. Первый в мире полноценный Linq провайдер работающий через WCF.

Круто! Вот только хотелось бы использовать Linq over WCF из Silverlight
IT
IT
09.08.2010 08:12
Здравствуйте, Andy77, Вы писали:

IT>>Как говорится we are proud to announce the very first fully functional Linq provider over WCF. Первый в мире полноценный Linq провайдер работающий через WCF.


A>Круто! Вот только хотелось бы использовать Linq over WCF из Silverlight


Над этим работаем. К сожалению, API SL в плане совместимости с FW вещь далеко не идеальная, поэтому придётся много поменять в булките, например, заменить все Hashtable на Dictionary и т.п.
koandrew
koandrew
12.08.2010 07:53
Здравствуйте, IT, Вы писали:

IT>Над этим работаем. К сожалению, API SL в плане совместимости с FW вещь далеко не идеальная, поэтому придётся много поменять в булките, например, заменить все Hashtable на Dictionary и т.п.


public class Hashtable : Dictionary<object, object> {}


Такой способ не пойдёт?
AndrewVK
AndrewVK
13.08.2010 09:10
Здравствуйте, koandrew, Вы писали:

K>Такой способ не пойдёт?


Ты действительно думаешь, что проблема в механическом рефакторинге?
... << RSDN@Home 1.2.0 alpha 4 rev. 1476 on Windows 7 6.1.7600.0>>
koandrew
koandrew
13.08.2010 04:15
Здравствуйте, AndrewVK, Вы писали:

AVK>Ты действительно думаешь, что проблема в механическом рефакторинге?


Я всего лишь предложил путь, которым я сам раньше воспользовался...
IT
IT
13.08.2010 03:47
Здравствуйте, koandrew, Вы писали:

K>
public class Hashtable : Dictionary<object, object> {}


K>Такой способ не пойдёт?


Нет. Hashtable отличается от Dictionary:

— способом добавления элемента в словать,
— способом проверки существования элемента в словаре,
— поддержкой многопоточности.
koandrew
koandrew
13.08.2010 04:15
Здравствуйте, IT, Вы писали:

IT>Нет. Hashtable отличается от Dictionary:


IT>- способом добавления элемента в словать,

Hashtable.Add(object, object)
Dictionary<TKey.TValue>.Add(TKey key, TValue value) == Dictionary<object, object>.Add(object, object)
=> одинаково
IT>- способом проверки существования элемента в словаре,
Оба класса юзают метод ContainsKey
IT>- поддержкой многопоточности.
Тут есть небольшые различия, это да. Но проблема в принципе решаемая.
AndrewVK
AndrewVK
13.08.2010 04:34
Здравствуйте, koandrew, Вы писали:

IT>>- способом проверки существования элемента в словаре,

K>Оба класса юзают метод ContainsKey

Знаешь почему у Hashtable нет метода TryGetValue?

IT>>- поддержкой многопоточности.

K>Тут есть небольшые различия, это да.

Различия весьма серьезные.

K> Но проблема в принципе решаемая.


Простым способом — нет. А сложным — проще отрефакторить.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476 on Windows 7 6.1.7600.0>>
koandrew
koandrew
13.08.2010 04:44
Здравствуйте, AndrewVK, Вы писали:

AVK>Знаешь почему у Hashtable нет метода TryGetValue?

Потому что хэштаблица выросла из первого фреймворка и там этого метода не было? И дженериков там тоже не было для избежания боксинга?
Этот метод — комбинация метода ContainsKey и индексёра. Оба исполняются за константное время. Цитирую MSDN:

This method combines the functionality of the ContainsKey method and the Item property.

If the key is not found, then the value parameter gets the appropriate default value for the value type TValue; for example, 0 (zero) for integer types, false for Boolean types, and nullNothingnullptra null reference (Nothing in Visual Basic) for reference types.

Use the TryGetValue method if your code frequently attempts to access keys that are not in the dictionary. Using this method is more efficient than catching the KeyNotFoundException thrown by the Item property.

This method approaches an O(1) operation.

Так что проблемы тут нет.

AVK>Различия весьма серьезные.

multiread-singlewrite у хэштаблицы против multiread у словаря. При необходимости допиливается через перекрытие методов после наследования от словаря.

AVK>Простым способом — нет. А сложным — проще отрефакторить.

Мой поинт в том, что может оказаться проще сваять класс Hashtable с нужным поведением (да хоть тупо выдернув его код из "большого" фреймворка рефлектором и собрав под Silverlight), чем выкорчёвывать его из текущей либы, заменяя за словари и втыкая локи...
IT
IT
13.08.2010 05:43
Здравствуйте, koandrew, Вы писали:

IT>>- способом добавления элемента в словать,

K>Hashtable.Add(object, object)
K>Dictionary<TKey.TValue>.Add(TKey key, TValue value) == Dictionary<object, object>.Add(object, object)
K>=> одинаково

hashTable[key] = value;

Если записи с key в таблице нет, то она будет добавлена. На этом можно строить логику.

IT>>- способом проверки существования элемента в словаре,

K>Оба класса юзают метод ContainsKey

var value = hashTable[key];

Если ключа нет в таблице, то вернётся null. Для Dictionary будет исключение.

IT>>- поддержкой многопоточности.

K>Тут есть небольшые различия, это да. Но проблема в принципе решаемая.

Все проблемы решаемые, но для этого нужно повозиться.
koandrew
koandrew
13.08.2010 05:54
Здравствуйте, IT, Вы писали:

IT>hashTable[key] = value;


IT>Если записи с key в таблице нет, то она будет добавлена. На этом можно строить логику.



IT>var value = hashTable[key];


IT>Если ключа нет в таблице, то вернётся null. Для Dictionary будет исключение.


Обе проблемы элементарно решаются переопределением индексёра с добавлением желаемого поведения.
Короче, если вам "ехать", а не шашечки, то больших проблем я не наблюдаю...
cadet354
cadet354
18.08.2010 06:53
Здравствуйте, IT, Вы писали:

IT>Как говорится we are proud to announce the very first fully functional Linq provider over WCF. Первый в мире полноценный Linq провайдер работающий через WCF.


уже есть другой, но платный
IT
IT
18.08.2010 06:55
Здравствуйте, cadet354, Вы писали:

IT>>Как говорится we are proud to announce the very first fully functional Linq provider over WCF. Первый в мире полноценный Linq провайдер работающий через WCF.

C>уже есть другой, но платный

Это полноценный over WCF или поддержка WCF RIA Services?
Кэр
Кэр
03.09.2010 05:23
Здравствуйте, IT, Вы писали:

IT>Как говорится we are proud to announce the very first fully functional Linq provider over WCF. Первый в мире полноценный Linq провайдер работающий через WCF.


Я только мельком глянул — но здесь вроде описывается похожий провайдер:
http://www.hanselman.com/blog/CreatingAnODataAPIForStackOverflowIncludingXMLAndJSONIn30Minutes.aspx

Там запросы описанны с помощью OData/Rest (параметры запроса).

Это аналог? Если да — то буду благодарен, если кто опубликует сравнительные характеристики. Если нет — то что я пропустил?
IT
IT
03.09.2010 10:00
Здравствуйте, Кэр, Вы писали:

IT>>Как говорится we are proud to announce the very first fully functional Linq provider over WCF. Первый в мире полноценный Linq провайдер работающий через WCF.


Кэр>Я только мельком глянул — но здесь вроде описывается похожий провайдер:

Кэр>http://www.hanselman.com/blog/CreatingAnODataAPIForStackOverflowIncludingXMLAndJSONIn30Minutes.aspx

Кэр>Там запросы описанны с помощью OData/Rest (параметры запроса).


Кэр>Это аналог? Если да — то буду благодарен, если кто опубликует сравнительные характеристики. Если нет — то что я пропустил?


WCF Data Services хотя и содержат в своём названии WCF, но на самом деле, как ты совершенно справедливо заметил, это скорее Linq over OData со всеми вытекающими последствиями.

Принципиальное отличие BLT в том, что ты можешь конфигурировать Linq отдельно, WCF отдельно, а потом просто сказать этим парням — поехали.

ЗЫ. Ещё к родственным технологиям надо бы добавить RIA Services. Но они только для SL.
Кэр
Кэр
03.09.2010 10:06
Здравствуйте, IT, Вы писали:

IT>WCF Data Services хотя и содержат в своём названии WCF, но на самом деле, как ты совершенно справедливо заметил, это скорее Linq over OData со всеми вытекающими последствиями.


IT>Принципиальное отличие BLT в том, что ты можешь конфигурировать Linq отдельно, WCF отдельно, а потом просто сказать этим парням — поехали.


Понял. Они там утверждают, что гибкость все равно осталась. Но она уже не такого уровня это точно.

Но с другой стороны в случае Linq over OData — я получаю удаленные Linq запросы и поддержку целый толпы клиентов — правильно? Сейчас в эпоху всех этих silverlight/objective-C/java это начинает таки иметь значение.
Alex Krasov
Alex Krasov
10.09.2010 04:29
Здравствуйте, IT, Вы писали:

IT>Самый больной вопрос в данном подходе, конечно же, безопасность, если она актуальна.

Очень актуальна, особенно учитывая возможности BLToolkit over WCF! Насколько я понял, LinqService позволяет исполнить любой select запрос (ExecuteReader) к любой таблице в базе данных, даже если эта таблица не представлена в виде класса модели данных на клиенте. Конечно, такая задача потребует понимания механизма сериализации LinqServiceQuery, но для потенциального "злоумышленника" больших проблем не составит, так как код всегда под рукой.

>LinqService содержит метод ValidateQuery, который можно использовать для валидации запроса.

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

>Например, на использование несанкционированных таблиц или полей. Собственно говоря, на эту тему хотелось бы побольше поговорить и услышать мнения насчёт типовых сценариев защиты, которые можно было бы реализовать прямо в библиотеке, т.к. хотя SqlQuery и можно анализировать, но в языках не поддерживающих patten matching это занятие не из весёлых.


На мой взгляд стоит реализовать basic securty на подобии WCF Data Services где можно задавать права доступа (Read, Write в вариациях — EntitySetRights Enumeration) на необходимые таблицы.

Так как модель данных в BLT over WCF реализации находится на клиенте, а сервер получает созданный на клиенте LinqServiceQuery, то также необходим контроль за списком полей, которыми клиент может манипулировать (не только запрашивать, но и использовать в Where). Наверное стоит реализовать возможность указывать список полей доступных для клиента для каждой из таблиц. По умолчанию можно отдавать все, но иметь возможность указать 'запрещенные' поля. Такого в WCF Data Services нет, так как там модель данных создается на сервере, соответственно клиент гарантировано получит только те поля, которые предоставляются сервисом.

Остается вопрос rowlevel security.
Задача примерно следующая:
— в зависимости от текущего service security context (user) необходимо вернуть набор записей, доступных этому пользователю.
В WCF Data Services решение такое:
— для каждой из таблиц можно задать optional QueryInterceptor который возвращает predicate, обязательный для этой таблицы.
Это дает возможность фильтровать возвращаемые данные на сервере в дополнение к запросу, созданному на клиенте.

В BLT можно было бы (наверное?) дополнять where необходимым фильтром на сервере.

Реализуя такие возможности out of the box, BLT позволит легко решать большинство задач связанных с security без дополнительного кода (код для объявления permissions — не в счет). В свою очередь это даст возможность грамотно и безопасно использовать BLT в WCF\Silverlight проектах большинству разработчиков.

Вообще реализация BLT over WCF — вещь шикарная — спору нет . Но именно из-за предоставляемой гибкости требует обязательной реализации ValidateQuery метода на сервере (на сегодняшний день).

Большое человеческое СПАСИБО за отличную реализацию. Однозначно будем продолжать использовать BLT в наших проектах.
IT
IT
10.09.2010 05:22
Здравствуйте, Alex Krasov, Вы писали:

Хорошо бы ещё прикинуть как это всё должно выглядеть.
Alex Krasov
Alex Krasov
12.09.2010 04:16
Здравствуйте, IT, Вы писали:

IT>Здравствуйте, Alex Krasov, Вы писали:


IT>Хорошо бы ещё прикинуть как это всё должно выглядеть.


предлагаю декларативное описание Permissions примерно следующего вида:

        static TestService()
        {
            SetPermissions("Customers", DataSourceRights.Read, "CustomerId", "FirstName", "LastName");
            SetPermissions("Orders", DataSourceRights.Read|DataSourceRights.Write);
        }


LinqService дополняется чем-то вроде:

        private static readonly Dictionary<string, TablePermissions> DataSecurity = new Dictionary<string, TablePermissions>();

        public static Func<string, string, bool> DemandFieldPermission = (tableName, fieldName) =>
                                                {
                                                    TablePermissions tablePermissions;
                                                    if (!DataSecurity.TryGetValue(tableName, out tablePermissions))
                                                        return false;
                                                    return tablePermissions.Fields==null || tablePermissions.Fields.Contains(fieldName);
                                                };

        public static Func<string, DataSourceRights, bool> DemandTablePermission = (tableName, rights) =>
                                                {
                                                    TablePermissions tablePermissions;
                                                    if (!DataSecurity.TryGetValue(tableName,out tablePermissions))
                                                        return false;
                                                    return (tablePermissions.Rights & rights) != 0;
                                                };

        protected static void SetPermissions(string tableName, DataSourceRights dataSourceRights, params string[] fields)
        {
            DataSecurity.Add(tableName,
                             new TablePermissions
                                 {
                                     TableName = tableName,
                                     Rights = dataSourceRights,
                                     Fields = fields == null ? null : new List<string>(fields)
                                 });
        }


Вопрос:
— откуда вызывать DemandFieldPermission и DemandTablePermission?
Может непосредственно из QueryDeserializer.Parse() примерно в таком виде:

                    case (int)QueryElementType.SqlTable :
                        {
                            var sourceID           = ReadInt();
                            var name               = ReadString();
                            var alias              = ReadString();
                            var database           = ReadString();
                            var owner              = ReadString();
                            var physicalName       = ReadString();
                            var objectType         = Read<Type>();
                            var sequenceAttributes = null as SequenceNameAttribute[];
#if !SILVERLIGHT
                            if (!LinqService.DemandTablePermission(name, demandingPermission))
                                throw new SecurityAccessDeniedException(string.Format("{0} access denied for {1}", demandingPermission, alias));
#endif

В этом случае удастся избежать повторного обхода десериализованного запроса, но я не уверен или можно вычислить demandingPermission из текущего контекста непосредственно в процессе десериализации.
Вопрос с удобным описанием RowLevel security пока остается открытым. Не имя модели данных на сервере можно пытаться манипулировать конструкциями вида SqlQuery.Predicate.ExprExpr(expr1, @operator, expr2), но это не удобно... Может у автора есть идеи как можно наиболее простым и интуитивно-понятным способом описать предикат не имея под рукой модели данных...?
Если предположить, что модель данных таки доступна для сервера и можно создать человеческий Expression<Func<T, bool>>, как его вставить в уже собранный BLT Query? Помнится после долгих танцев с бубном в EF 4 мне нечто подобное так и не удалось...
AndrewVK
AndrewVK
12.09.2010 02:25
Здравствуйте, Alex Krasov, Вы писали:

AK>Вопрос с удобным описанием RowLevel security пока остается открытым.


Вопрос с RLS вообще отдельный, к WCF отношения почти не имеющий. И решить этот вопрос универсально невозможно, не стоит даже пытаться. Универсальная схема в современных БД тормозит очень сильно, поэтому на практике обычно используют всякие эвристики.

AK>Может у автора есть идеи как можно наиболее простым и интуитивно-понятным способом описать предикат не имея под рукой модели данных...?


А почему не имея модели?

AK>Если предположить, что модель данных таки доступна для сервера


А как иначе?

AK> и можно создать человеческий Expression<Func<T, bool>>, как его вставить в уже собранный BLT Query?


Вот про это IT и спрашивает. Уже сейчас есть ряд механик, позволяющих в определенной мере доконструировать запрос, но для секурности они не очень удобны. Поэтому нужно придумать, как такое описать, чтобы в большинстве случаев это было красиво и удобно.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476 on Windows 7 6.1.7600.0>>
Alex Krasov
Alex Krasov
13.09.2010 03:14
Здравствуйте, AndrewVK, Вы писали:

AVK>Вопрос с RLS вообще отдельный, к WCF отношения почти не имеющий. И решить этот вопрос универсально невозможно, не стоит даже пытаться. Универсальная схема в современных БД тормозит очень сильно, поэтому на практике обычно используют всякие эвристики.

Универсально не получится — согласен, но думалось сделать некий helper в дополнение к ValidateQuery который упростил бы создание предиката для фильтра записей по необходимому условию. Вообще, хотя данный вопрос и был поднят в контексте securiy, думается было бы полезно попытаться реализовать простой(упрощенный?) механизм для манипулирования LinqServiceQuery на сервере.
AVK>А почему не имея модели?
AK>>Если предположить, что модель данных таки доступна для сервера

AVK>А как иначе?

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

AK>> и можно создать человеческий Expression<Func<T, bool>>, как его вставить в уже собранный BLT Query?


AVK>Вот про это IT и спрашивает. Уже сейчас есть ряд механик, позволяющих в определенной мере доконструировать запрос, но для секурности они не очень удобны.

А можно какой-нить пример такой деконструкции (я еще лучше — конструкции)? Кроме как использования классов из семейства SqlQuery.Predicate.* для описания нужного предиката на стороне сервера ничего на ум не приходит...

AVK>Поэтому нужно придумать, как такое описать, чтобы в большинстве случаев это было красиво и удобно.

Что до большинства случаев — так для них как раз и подошло бы описание вида Expression<Func<T, bool>> — это просто, но позволит описать security filter для небольших задач.
AndrewVK
AndrewVK
13.09.2010 03:31
Здравствуйте, Alex Krasov, Вы писали:

AK>Универсально не получится — согласен, но думалось сделать некий helper в дополнение к ValidateQuery который упростил бы создание предиката для фильтра записей по необходимому условию.


Для этого нужно понять, какие вообще бывают подобные предикаты. Именно в этом суть исходного вопроса и состояла.

AVK>>А как иначе?

AK>Так ведь для выполнения LinqServiceQuery не нужна она...

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

AK>А можно какой-нить пример такой деконструкции (я еще лучше — конструкции)?


Например то, как собственные sql-функции добавляются. Или как создаются кастомные подзапросы для методов.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476 on Windows 7 6.1.7600.0>>
adontz
adontz
13.09.2010 03:33
Здравствуйте, AndrewVK, Вы писали:

AK>>Универсально не получится — согласен, но думалось сделать некий helper в дополнение к ValidateQuery который упростил бы создание предиката для фильтра записей по необходимому условию.

AVK>Для этого нужно понять, какие вообще бывают подобные предикаты. Именно в этом суть исходного вопроса и состояла.

Возможно, имеетс мысл создать два уровня фильтраци: на уровне БД, который должен съекономить чтение с диска и на уровне сервера приложений, который уже под чистую всё отфильтрует. Читать с БД в полтора-два раза больше чем надо и дофильтровывать уже потом не такое уж и плохое решение если учесть конечную скорость разработки.
Alex Krasov
Alex Krasov
13.09.2010 05:54
Здравствуйте, AndrewVK, Вы писали:


AVK>Не совсем так. Откидывание на клиента структуры БД не есть разумный вариант. В идеале на клиенте должна быть чистая модель, а модель с маппингом уже на сервере. Маппинг, как ты сам понимаешь, без модели быть не может. А маппинг на клиенте годится только для игрушечных приложений.


Что-то я упускаю — даже после повторного прохода по коду не вижу где бы можно было вклинится с маппингом серверной модели данных. На сколько я разобрался, вся информация для запроса (включая весь mapping) подготавливается на стороне клиента, а на сервере производится построение описанного в SqlQuery (собирается из LinqServiceQuery) SQL выражения с помощью конкретного SqlQueryProvider. В каком слое происходит (может произойти?) маппинг с помощью сервеной модели? То, что можно руками допилить собранный SqlQuery на сервере — понятно. Для того нам ValidataQuery и был дан 'сверху' Я пытаюсь понять или есть возможность использовать на сервере стандартный model mapping подход, применяемый в BLT, к запросу построенному на клиенте. В качестве стандарта я подразумеваю описание POCO модели со всеми необходимыми mapping attributes.
pr0ff
pr0ff
10.09.2010 08:21
+ за аналог QueryInterceptor
ИМХО самый простой и эффективный метод. Только, конечно, надо будет не забыть применять их ко всем связям

P.S. правда в случае ошибки доступа будет not found, а не access denied, но в большинстве случаев это неважно
... << RSDN@Home 1.2.0 alpha 4 rev. 1472>>
Alex Krasov
Alex Krasov
10.09.2010 08:30
Здравствуйте, pr0ff, Вы писали:

P>+ за аналог QueryInterceptor

P>ИМХО самый простой и эффективный метод. Только, конечно, надо будет не забыть применять их ко всем связям

QueryInterceptor — это хорошо. Вот только модель наша — на клиенте, а без модели Expression по человечески не построишь... Отсюда вывод — генерить модель и для сервиса На сервисе она нам, скорее всего, все равно пригодится, а для построения Expression — необходима.
Есть идеи как еще можно задать этот самый Interceptor?
adontz
adontz
10.09.2010 05:32
Здравствуйте, IT, Вы писали:

А можно в двух словах в чём принципиальное отличие от двузвенки?
IT
IT
10.09.2010 06:03
Здравствуйте, adontz, Вы писали:

A>А можно в двух словах в чём принципиальное отличие от двузвенки?


Запрос контролируется сервером со всеми вытекающими последствиями. К тому же, бич двухзвенки — это рост количества соединений к серверу БД пропорционально количеству пользователей. При тысячах пользователей сервера БД просто дают дуба.
adontz
adontz
10.09.2010 06:47
Здравствуйте, IT, Вы писали:

A>>А можно в двух словах в чём принципиальное отличие от двузвенки?

IT>Запрос контролируется сервером со всеми вытекающими последствиями.

Зависит от уровня контроля. Насколько легко организовать row level security по группе таблиц?
IT
IT
10.09.2010 06:54
Здравствуйте, adontz, Вы писали:

A>>>А можно в двух словах в чём принципиальное отличие от двузвенки?

IT>>Запрос контролируется сервером со всеми вытекающими последствиями.

A>Зависит от уровня контроля. Насколько легко организовать row level security по группе таблиц?


Этот вопрос как раз сейчас решается. При этом интересно прежде всего как это должно выглядеть. Как бы ты хотел, чтобы это выглядело?
adontz
adontz
10.09.2010 07:09
Здравствуйте, IT, Вы писали:

A>>Зависит от уровня контроля. Насколько легко организовать row level security по группе таблиц?

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

Ну у меня вообще говоря задачи весьма специфические, но если это чем-то поможет расскажу.

Права доступа могу даваться как на класс объектов (можно/нельзя кушать булки вообще), так и на конкретные объекты (можно/нельзя кушать конкретную булку).
Задаются они весьма развесисто, но благодаря триггерам и такой-то матери всё это выливается в табличку Employee GUID, Target GUID, Permissions INT, где к качестве Target может выступать как GUID объекта, так и GUID класса. Соответсвенно любой запрос предваряется проверкой можно ли оперировать с объектами необходимых классов. Если можно, то обрабатываются все объекты (строки) для которых операция не запрещена (LEFT OUTER JOIN, ISNULL), иначе все те для которых разрешена. Выглядит примено так
CREATE PROCEDURE [dbo].[ImageTag::CreateInstance](
    @SecurityContext UNIQUEIDENTIFIER,
    @ID UNIQUEIDENTIFIER,
    @Format INT,
    @Family NVARCHAR(16),
    @Data VARBINARY(MAX),
    @Target_Type UNIQUEIDENTIFIER,
    @Target_ID UNIQUEIDENTIFIER)
AS
BEGIN
    DECLARE @SecurityResult INT;

    EXECUTE @SecurityResult = [~EmployeePermissions::Check] @SecurityContext, '37caf5e2-4128-415f-9dbb-01322ec06f0a', NULL, 1;

    IF @SecurityResult = 0
        RAISERROR (N'ACCESS DENIED;ImageTag;CreateInstance', 16, 0);

    INSERT INTO
        [ImageTag]([ID], [Format], [Family], [Data], [Target/Type], [Target/ID])
    VALUES
        (@ID, @Format, @Family, @Data, @Target_Type, @Target_ID);
END
CREATE PROCEDURE [dbo].[ImageTag::ReadAllInstances](
    @SecurityContext UNIQUEIDENTIFIER)
AS
BEGIN
    DECLARE @SecurityResult INT;
    DECLARE @DefaultPermissions INT;

    EXECUTE @SecurityResult = [~EmployeePermissions::Check] @SecurityContext, '37caf5e2-4128-415f-9dbb-01322ec06f0a', NULL, 2048;

    IF @SecurityResult = 0
    BEGIN
        SET @DefaultPermissions = 0
    END
    ELSE
    BEGIN
        SET @DefaultPermissions = 2048
    END

    SELECT
        [ID],
        [Format],
        [Family],
        [Data],
        [Target/Type],
        [Target/ID]
    FROM
        [ImageTag]
    WHERE
        (@DefaultPermissions = 2048)
END
adontz
adontz
10.09.2010 07:15
Здравствуйте, adontz, Вы писали:


CREATE PROCEDURE [dbo].[Account::ReadAllInstances](
    @SecurityContext UNIQUEIDENTIFIER)
AS
BEGIN
    DECLARE @SecurityResult INT;
    DECLARE @DefaultPermissions INT;

    EXECUTE @SecurityResult = [~EmployeePermissions::Check] @SecurityContext, '0e490d07-556d-41c4-86aa-0689dee1c98a', NULL, 2048;

    IF @SecurityResult = 0
    BEGIN
        SET @DefaultPermissions = 0
    END
    ELSE
    BEGIN
        SET @DefaultPermissions = 2048
    END

    SELECT
        [ID],
        [HierarchyID],
        [HierarchyLevel],
        [HierarchyParent],
        [Code],
        [Name],
        [Employee],
        [Family],
        [Organization]
    FROM
        [Account]
    LEFT OUTER JOIN
        [~EmployeePermissions] ON [~EmployeePermissions].[~Target] = [Account].[ID]
    WHERE
        (ISNULL([~EmployeePermissions].[~Permissions], @DefaultPermissions) & 2048) <> 0
END
IT
IT
10.09.2010 07:18
Здравствуйте, adontz, Вы писали:

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

A>Ну у меня вообще говоря задачи весьма специфические, но если это чем-то поможет расскажу.

Это всё очень интересно, но хотелось бы прикинуть как должен выглядеть API управления настройками LinqService, который потом будет по этим правилам перетряхивать запросы.
adontz
adontz
10.09.2010 07:26
Здравствуйте, IT, Вы писали:

IT>Это всё очень интересно, но хотелось бы прикинуть как должен выглядеть API управления настройками LinqService, который потом будет по этим правилам перетряхивать запросы.


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

Могу только резюмировать: row-level security надо реализовывать на уровне БД. Выкачивать зиллион строк в Application Server чтобы в конце выдать access denied идея далёкая от хорошей. Следовательно, надо уметь как-то сообщать SQL запросу в контексте какого пользователя приложения он выполняется. Видимо, надо внедрять служебный параметр. Но откуда его брать? Нужно как-то интегрироваться с аутентификацией. Для авторизации доступа мало ковырять WHERE, нужен JOIN. В моём примере всё симметрично, у всех таблиц есть идентификатор GUID и все права доступа в одной таблице. Вообще говоря это может быть не так. И всё это, видимо, должно хорошо работать для подзапросов.
adontz
adontz
11.09.2010 02:05
Здравствуйте, IT, Вы писали:

IT>Это всё очень интересно, но хотелось бы прикинуть как должен выглядеть API управления настройками LinqService, который потом будет по этим правилам перетряхивать запросы.


Ещё я тут подумал. Что делать с правами доступа проверяемыми не на уровне БД? Например, logon hours. Что делать с кешированием, журналированием (я хочу журналировать не только запросы)? ИМХО очень нерасширяемое решение получилось. Катит только как QuickStart. И преимущество у QuickStart тогда должно быть только одно — с него должно быть можно быстро и безболезненно соскочить.
WaSh
WaSh
03.02.2011 01:38
Здравствуйте, IT, Вы писали:

IT>Запустить пример в одном процессе можно примерно так:


IT>
IT>using (var host = new ServiceHost(new LinqService(), new Uri("net.tcp://localhost:1234")))
IT>{
.....

IT>    var list = ctx.GetTable<Person>().ToList();

........
IT>}
IT>


Что делать в случае, если таблица Person большая ? А тем не менее хотелось бы выбрать всю таблицу ? Может ли помочь изменение " MaxStringContentLength property on the XmlDictionaryReaderQuotas " и как до него добраться ?


У меня вываливается исключение

System.ServiceModel.Dispatcher.NetDispatcherFaultException was unhandled
Message=The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:ExecuteReaderResult. The InnerException message was 'There was an error deserializing the object of type BLToolkit.ServiceModel.LinqServiceResult. The maximum string content length quota (8192) has been exceeded while reading XML data. This quota may be increased by changing the MaxStringContentLength property on the XmlDictionaryReaderQuotas object used when creating the XML reader.'. Please see InnerException for more details.
Source=mscorlib
Action=http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher/fault
StackTrace:
Server stack trace:
at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeParameterPart(XmlDictionaryReader reader, PartInfo part, Boolean isRequest)
at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeParameter(XmlDictionaryReader reader, PartInfo part, Boolean isRequest)
at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeBody(XmlDictionaryReader reader, MessageVersion version, String action, MessageDescription messageDescription, Object[] parameters, Boolean isRequest)
at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeBodyContents(Message message, Object[] parameters, Boolean isRequest)
at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeReply(Message message, Object[] parameters)
at System.ServiceModel.Dispatcher.ProxyOperationRuntime.AfterReply(ProxyRpc& rpc)
at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at BLToolkit.ServiceModel.ILinqService.ExecuteReader(LinqServiceQuery query)
at BLToolkit.ServiceModel.LinqServiceClient.ExecuteReader(LinqServiceQuery query) in d:\GeneratedCode\9\Test\BLToolkit\ServiceModel\LinqServiceClient.cs:line 43
at BLToolkit.ServiceModel.ServiceModelDataContext.BLToolkit.Data.Linq.IDataContext.ExecuteReader(Object query) in d:\GeneratedCode\9\Test\BLToolkit\ServiceModel\ServiceModelDataContext.cs:line 210
at BLToolkit.Data.Linq.Query`1.<RunQuery>d__2a.MoveNext() in d:\GeneratedCode\9\Test\BLToolkit\Data\Linq\Query.cs:line 367
at BLToolkit.Data.Linq.Query`1.<Map>d__32.MoveNext() in d:\GeneratedCode\9\Test\BLToolkit\Data\Linq\Query.cs:line 383
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Program.Main() in D:\GeneratedCode\9\Test\Test\Program.cs:line 106
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException: System.Runtime.Serialization.SerializationException
Message=There was an error deserializing the object of type BLToolkit.ServiceModel.LinqServiceResult. The maximum string content length quota (8192) has been exceeded while reading XML data. This quota may be increased by changing the MaxStringContentLength property on the XmlDictionaryReaderQuotas object used when creating the XML reader.
Source=System.Runtime.Serialization
StackTrace:
at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.DataContractSerializer.ReadObject(XmlDictionaryReader reader, Boolean verifyObjectName)
at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeParameterPart(XmlDictionaryReader reader, PartInfo part, Boolean isRequest)
InnerException: System.Xml.XmlException
Message=The maximum string content length quota (8192) has been exceeded while reading XML data. This quota may be increased by changing the MaxStringContentLength property on the XmlDictionaryReaderQuotas object used when creating the XML reader.
Source=System.Runtime.Serialization
LineNumber=0
LinePosition=0
StackTrace:
at System.Xml.XmlExceptionHelper.ThrowXmlException(XmlDictionaryReader reader, String res, String arg1, String arg2, String arg3)
at System.Xml.XmlExceptionHelper.ThrowMaxStringContentLengthExceeded(XmlDictionaryReader reader, Int32 maxStringContentLength)
at System.Xml.XmlDictionaryReader.ReadContentAsString(Int32 maxStringContentLength)
at System.Xml.XmlBaseReader.ReadContentAsString()
at System.Xml.XmlBaseReader.ReadElementContentAsString()
at System.Xml.XmlBinaryReader.ReadElementContentAsString()
at System.Runtime.Serialization.XmlReaderDelegator.ReadElementContentAsString()
at ReadLinqServiceResultFromXml(XmlReaderDelegator , XmlObjectSerializerReadContext , XmlDictionaryString[] , XmlDictionaryString[] )
at System.Runtime.Serialization.ClassDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)
at System.Runtime.Serialization.XmlObjectSerializerReadContext.ReadDataContractValue(DataContract dataContract, XmlReaderDelegator reader)
at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator reader, String name, String ns, Type declaredType, DataContract& dataContract)
at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator xmlReader, Type declaredType, DataContract dataContract, String name, String ns)
at System.Runtime.Serialization.DataContractSerializer.InternalReadObject(XmlReaderDelegator xmlReader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
InnerException:

Мож
... << RSDN@Home 1.2.0 alpha 4 rev. 1481>>
AndrewVK
AndrewVK
03.02.2011 01:56
Здравствуйте, WaSh, Вы писали:

WS>Что делать в случае, если таблица Person большая ? А тем не менее хотелось бы выбрать всю таблицу ? Может ли помочь изменение " MaxStringContentLength property on the XmlDictionaryReaderQuotas " и как до него добраться ?


Помочь может. Как поменять — подробно описано в MSDN. Вкратце — или в конфиге ендпоинта, или в соотв. элементах описания биндинга при программном конфигурировании. Поиск в MSDN по MaxStringContentLength должен помочь.
... << RSDN@Home 1.2.0 alpha 4 rev. 1490 on Windows 7 6.1.7600.0>>
WaSh
WaSh
07.02.2011 07:08
Помогло.. только нужно устанавливать свойства бинидинга как на хосте, так и на клиенте
var binding = new NetTcpBinding(SecurityMode.None)
{
    MaxReceivedMessageSize = 10000000,
    MaxBufferPoolSize      = 10000000,
    MaxBufferSize          = 10000000,
    CloseTimeout           = new TimeSpan(00, 01, 00),
    OpenTimeout            = new TimeSpan(00, 01, 00),
    ReceiveTimeout         = new TimeSpan(00, 10, 00),
    SendTimeout            = new TimeSpan(00, 10, 00),
};

binding.ReaderQuotas.MaxStringContentLength = Int32.MaxValue;
... << RSDN@Home 1.2.0 alpha 4 rev. 1481>>
AndrewVK
AndrewVK
07.02.2011 03:04
Здравствуйте, WaSh, Вы писали:

WS>Помогло.. только нужно устанавливать свойства бинидинга как на хосте, так и на клиенте


Разумеется. На сервере реальный контроль, на клиенте валидация, чтобы лишний раз сервер не нагружать.
... << RSDN@Home 1.2.0 alpha 5 rev. 1495 on Windows 7 6.1.7600.0>>
swimmers
swimmers
17.04.2012 02:41
Скачал последнюю версию.
Все запросы Linq Over WCF не работают, отваливаются по таймауту.
До этого использовалась версия 4.0, все работало.
Сейчас не работает даже этот пример, см. ниже.

В чем подвох и как лечить?


using (var host = new ServiceHost(new LinqService(), new Uri("net.tcp://localhost:1234")))
{
    host.Description.Behaviors.Add(new ServiceMetadataBehavior());
    host.Description.Behaviors.Find<ServiceDebugBehavior>().IncludeExceptionDetailInFaults = true;
    host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexTcpBinding(), "mex");
    host.AddServiceEndpoint(
        typeof(ILinqService),
        new NetTcpBinding(SecurityMode.None)
        {
            MaxReceivedMessageSize = 10000000,
            MaxBufferPoolSize      = 10000000,
            MaxBufferSize          = 10000000,
            CloseTimeout           = new TimeSpan(00, 01, 00),
            OpenTimeout            = new TimeSpan(00, 01, 00),
            ReceiveTimeout         = new TimeSpan(00, 10, 00),
            SendTimeout            = new TimeSpan(00, 10, 00),
        },
        "LinqOverWCF");

    host.Open();

    var ctx = new ServiceModelDataContext(
        new NetTcpBinding(SecurityMode.None)
        {
            MaxReceivedMessageSize = 10000000,
            MaxBufferPoolSize      = 10000000,
            MaxBufferSize          = 10000000,
            CloseTimeout           = new TimeSpan(00, 01, 00),
            OpenTimeout            = new TimeSpan(00, 01, 00),
            ReceiveTimeout         = new TimeSpan(00, 10, 00),
            SendTimeout            = new TimeSpan(00, 10, 00),
        },
        new EndpointAddress("net.tcp://localhost:1234/LinqOverWCF"));

    var list = ctx.GetTable<Person>().ToList();

    host.Close();

    return list;
}