Для упрощения процесса вызова сохраненных процедур из SQL Server написал я себе такой класс на C#,
использующий зачатки аспектно-ориентированного программирования, насколько это AOP это, конечно
спорный вопрос, но концепция получилась довольно интересная.
Код пока не идеальный (надо будет пройтись пару раз) но работает стабильно.
Смысл следующий. Для вызова сохраненных процедур из SQL сервер надо создать класс производный
от SQLSrvSPClass. Все функции добавленные в этот класс при вызове будут реально вызывать сохраненые
процедуры на SQL Server, с таким же именем и аргументами как и имя функции в классе.
Для параметров (Output,InputOutput) в С# функции надо указать параметр как ref (или out)
и они будут возвращены при вызове. Типы параметров в C# функции должны быть эквивалентными типам в SQL процедуре.
Например:
class SQLSrv : SQLSrvSPClass
{
public SQLSrv(SqlConnection conn):base(conn){}
public DataSet MyProc(int Par1,int Par2,ref int OutPar){return null;}
//для возвращаемого значения можно написать: DataSet,DataTable,SqlDataReader,void
}
При вызове C# функции MyProc вызовется вызывается такая SQL SP, и возвращаются через ref выходные параметры.
PROCEDURE dbo.MyProc ( @Par1 int, @Par2 int, @OutPar int output) AS
set @OutPar= @Par1+@Par2
-- первый result setselect * from ...
-- второй result setselect * from ...
return 1
Такой функции в C# можно задавать 4 типа возвращаемых значений.
1)void в глубине реализации вызывается SqlCommand.ExecuteNonQuery
2)DataTable возвращается одна таблица (делается Addapter.Fill)
3)DataSet возвращаются все result set
4)SqlDataReader
Остальные возвращаемые значения не принимаются.
Класс SQLSrvSPClass это контекстно-связанный объект у которого перехватываются вызовы всех функций,
в том числе и для производных классов.
//Использование выглядит следующим образом:
using System;
using SQLSRVAspect;
using System.Data.SqlClient;
using System.Data;
//Все функции определенные в этом классе будут преобразованы
// в вызовы сохраненных процедур на SQL сервере,
// для SqlConnection переданного в конструктор класса
// Тела функций игнорируются.class SQLSrv : SQLSrvSPClass
{ //инициализировать SQLSrvSPClass класс только так,у базового класса только один protected конструкторpublic SQLSrv(SqlConnection conn):base(conn){}
public DataSet MyProc(int Par1,int Par2,ref int OutPar){return null;}
// Если нужно получить код возврата из процедуры
// то можно дописать последний параметр с с именем ReturnValue он должен быть by ref
}
class Class1
{
[STAThread]
static void Main(string[] args)
{
SqlConnection con=new SqlConnection(@"....");
con.Open();
int outparam=0,retval=0;
SQLSrv srv=new SQLSrv(con);
// вызываем сохраненую процедуру из SQL Serverobject ds=srv.MyProc(10,20,ref outparam);
// выводим в консоль полученый объект
DataPrint.Print(ds);
con.Close();
Console.ReadLine();
}
}
// для отладочного вывода в консольpublic class DataPrint
{
public static void Print(object d)
{
if(d.GetType()==typeof(DataSet))
Print((DataSet)d);
else if(d.GetType()==typeof(DataTable))
Print((DataTable)d);
}
private static void Print(DataTable tbl)
{
Console.WriteLine("\n\nTable: "+tbl.TableName);
Console.WriteLine("---------------------");
foreach(DataRow dr in tbl.Rows)
{
Console.WriteLine();
foreach(object cel in dr.ItemArray)
Console.Write(cel.ToString()+"\t");
}
}
private static void Print(DataSet ds)
{
Console.WriteLine("\n DataSet \""+ds.DataSetName+"\n");
foreach(DataTable tbl in ds.Tables)
{
Print(tbl);
}
}
}
// Далее все идет реализация класса SQLSrvSPClass в трех файлах.
using System;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Messaging;
using System.Reflection;
namespace Aspect
{
[AttributeUsage(AttributeTargets.Class)]
abstract public class AspectAttribute : ContextAttribute
{
static string s_Name="name";
public AspectAttribute() : base(s_Name){}
public override void GetPropertiesForNewContext(IConstructionCallMessage ccm)
{
ccm.ContextProperties.Add(new AspectProperty(GetAspectType()));
}
abstract public Type GetAspectType();
protected class AspectProperty : IContextProperty,
IContributeObjectSink
{
public Type _AspectType=null;
public AspectProperty(Type type)
{
_AspectType=type;
}
public IMessageSink GetObjectSink(MarshalByRefObject o,IMessageSink next)
{
IMessageSink sink=(IMessageSink)Activator.CreateInstance(_AspectType);
object[] pars=new object[]{next,o};
_AspectType.InvokeMember("InitBase",BindingFlags.InvokeMethod,null,sink,pars);
return sink;
}
public void Freeze(Context newContext){}
public bool IsNewContextOK(Context newCtx){return true;}
public string Name{ get{return s_Name;} }
}
}
}
Здравствуйте Silver_s, Вы писали:
SS>Смысл следующий. Для вызова сохраненных процедур из SQL сервер надо создать класс производный от SQLSrvSPClass. Все функции добавленные в этот класс при вызове будут реально вызывать сохраненые процедуры на SQL Server, с таким же именем и аргументами как и имя функции в классе.
Круто. Хорошо бы об этом статейку написать
Если нам не помогут, то мы тоже никого не пощадим.
Re[2]: вызов SP из SQL Server на C#, через AOP оболочку
Здравствуйте IT, Вы писали:
IT>Здравствуйте Silver_s, Вы писали:
SS>>Смысл следующий. Для вызова сохраненных процедур из SQL сервер надо создать класс производный от SQLSrvSPClass. Все функции добавленные в этот класс при вызове будут реально вызывать сохраненые процедуры на SQL Server, с таким же именем и аргументами как и имя функции в классе.
IT>Круто. Хорошо бы об этом статейку написать
Ага можно будет сравнить два варианта подхода к подобным задачам (предложенный здесь и с использованием CustomProxy)
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re: вызов SP из SQL Server на C#, через AOP оболочку
На базе этого реализовал два класса AspectBaseClass и AspectAttribute, с помощью которых можно
легко создавать разные аспекты-перехватчики. Т.к. библиотечные средства очень низкого уровня.
Настолько низкого, что для классов и интерфейсов которые там используются,
даже в MSDN единственное что написано:
"...supports the .NET Framework infrastructure and is not intended to be used directly from your code."
весь namespace System.Runtime.Remoting.Contexts описан таким образом. Типа, не суйте нос куда не следует.
Тяжело в таких условиях разбираться в этих штучках.
Непосредственно класс вызывающий SQL SP реализован на основе этих классов (AspectBaseClass и AspectAttribute)
и делается это с помощью них довольно просто, как и написание любого другого перехватчика.
В принципе эти классы работают нормально, но есть еще что там поковырять и вылизать.
Ниже привел краткие комментарии к использованию этих классов.
Для написания перехватчика нужно:
1) Унаследоваться от класса AspectBaseClass и переопределить 3 функции:
void Init(MarshalByRefObject obj) — позволяет перехватчику получить объект к которому он прикреплен.
Это нужно если в пост- и препроцессной обработке потребуется доступ к данным объекта.
(Напр. для класса вызывающего SQL SP, перехватчик должен брать SqlConnection из объекта)
2) Далее надо создать атрибут унаследованный от AspectAttribute и настроить на нужный класс перехватчика.
class MyAspectAttribute: AspectAttribute
{
public override Type GetAspectType(){ return typeof(MyAspect); }
}
3) Для класса наследованного от ContextBoundObject, применение такого атрибута прикрепляет
к классу аспект-перехватчик.
[MyAspect]
class MyClass: ContextBoundObject
{
...
}
Все функции в таком классе будут перехватываться классом MyAspect. До вызова настоящей функции вызывается PreProcess в
перехватчике, а после вызова PostProcess. Обе функции перехватчика имеют полный доступ к метаданным функции
и к переданным и возвращаемым параметрам и могут менять их значения.
Перехват также происходит при доступе к полям класса (не property),
они перехватываются как FieldGetter, FieldSetter.
Иногда у класса могут вызываться лишние функции(которых в классе нету),
например при использовании класса в JScript Engine, этот Engine сразу после создания объекта пытается вызвать
функцию GetSite (или что-то типа того). Хотя ее в классе нет но перехватчик для нее вызывается.
Поэтому в PreProcess и PostProcess надежнее проверять через переданный MethodInfo в каком классе определена функция,
либо помечать функции атрибутами и проверять их через MethodInfo, чтобы не делать обработку для ненужных методов.
Конструкторы не перехватываются.
Re[3]: вызов SP из SQL Server на C#, через AOP оболочку
От:
Аноним
Дата:
16.08.02 15:06
Оценка:
Здравствуйте Silver_s, Вы писали:
SS>Насчет статьи не знаю, надо подумать получится ли что интересное.
Получится, получится. Я вот пытался разобраться в твоём коде, даже распечатал всё это хозяйство, но пока так и не уверен, что всё понимаю. Интересна именно как работает перехватчик. Так что это хорошая тема для статьи. Думаю, что можно как раз поспеть к следующему номеру RSDN Mag ;)
А TK нам напишет об альтернативном способе ;)
Re: вызов SP из SQL Server на C#, через AOP оболочку
Здравствуйте IT, Вы писали:
IT>Круто. Хорошо бы об этом статейку написать
ok. Доведу свою задумку до завершения и попробую написать.
Сейчас копаю в сторону динамического подключения и отключения перехватчиков в Runtime.
Прототип к которому пытаюсь привести выглядит так:
Для написания перехватчика надо унаследоваться от AspectBase и переопределить Preprocess, Postprocess.
А объект к которому будут цепляться перехватчики унаследовать от AspectBoundObject,
в нем будет неперехватываемое property AspectCollection (например ArrayList) в который можно добавлять
и удалять перехватчики.
class My: AspectBoundObject
{
void Func(int i){}
}
....
// создаем SQL аспект
SQLSrvAspect a1=SQLSrvAspect();
a1.Connection=....
//другой аспект, например замеряет длительность вызова метода и выводит время в консоль
SomeAspect a2=new SomeAspect();
My obj=new My();
obj.Func(1); //чистый вызов без перехвата
obj.AspectCollection.Add(a1);
obj.Func(1); //вызов через SQL
obj.AspectCollection.Add(a2);
obj.Func(1); //вызов через SQL, с выводом в консоль длительности вызова
obj.AspectCollection.Remove(a2); // отключаем аспект таймера
Re[3]: вызов SP из SQL Server на C#, через AOP оболочку
Здравствуйте Silver_s, Вы писали:
SS>Здравствуйте IT, Вы писали:
IT>>Круто. Хорошо бы об этом статейку написать
SS>ok. Доведу свою задумку до завершения и попробую написать. SS>Сейчас копаю в сторону динамического подключения и отключения перехватчиков в Runtime. SS>Прототип к которому пытаюсь привести выглядит так:
SS>Для написания перехватчика надо унаследоваться от AspectBase и переопределить Preprocess, Postprocess.
Только наверное не очень хорошо, что приходится наследоваться от какого-либо стороннего класса... Например при этом пропадает возможность подключения аспектов к классам созданными другими разработчиками (можно конечно для этого завести отдельный интерфейс и каждый раз его реализовавать, что тоже не лучший вариант), плюс сам класс будет знать о существовании подключенных аспектов, а значит, он может на них влиять и потенциально гадить...
Например в данном примере о наличии аспекта будет знать только тот, кто его подключил:
[AspectStack]
public class TestObject : ContextBoundObject
{
private string _property;
public TestObject()
{
}
public String Property
{
set
{
_property = value;
}
get
{
return _property;
}
}
public string TestMethod (string value)
{
Console.WriteLine("TestMethod:" + value);
return"TestMethod:" + value;
}
}
Здравствуйте TK, Вы писали:
TK>Только наверное не очень хорошо, что приходится наследоваться от какого-либо стороннего класса... Например при этом пропадает возможность подключения аспектов к классам созданными другими разработчиками
Да, навязывать свой базовый класс нехорошо — не удастся использовать уже существующие классы-наследники ContextBoundObject.
TK>сам класс будет знать о существовании подключенных аспектов, а значит, он может на них влиять и потенциально гадить...
Выполняющемуся методу конечно не желательно давать возможность подключать,отключать аспекты.
Но это вопрос неоднозначный — должен ли класс что-то знать о подключенных аспектах. В COM+ объекты имеют возможность получать информацию из аспектов-контекстов в которых выполняются. И вроде это используется. Напр. метод может запросить выполняется ли он в контексте транзакции и если нет то ругнуться. Это, вобще-то не трудно сделать при необходимости:
Через IMessage можно получить LogicalCallContext и туда засунуть объект, который можно извлечь через контекст при выполнении метода.
По поводу сравнения двух методов подключения ( ProxyAttribute и ContextAttribute ).
-----------------------
Тут как всегда.. Через ProxyAttribute более гибкий но более сложный способ так как перехват происходит на более ранней стадии.
Через ContextAttribute гораздо проще, но из-за недостаточной гибкости метода приходится извращаться, что в конечном итоге все усложняет.
При вызове метода в ContextBoundObject, Proxy получает упрвление первым и через Invoke передает IMessage вызова дальше на MessageSinks.
RealProxy.Invoke -> IMessageSink -> IMessageSink -> OriginalMethod
Эти IMessageSink как раз и создаются аттрибутами ContextProperty. Здесь ни чего особенного делать не надо, можно сказать атрибут нужен только для того чтобы вернуть IMessageSink перехватчика, цепочка выстраивается автоматически. Но этот встроенный механизм через ContextProperty слишком негибкий, чтобы на него полагаться — для каждого подключения аспекта надо добавлять атрибут, тяжело управлять их созданием и трудно до них потом добраться и.т.д.. Правда выход есть — можно создать один свой Sink и только к нему подключать дополнительные перехватчики. Тогда будет примерно то-же что и с перехватом RealProxy->Invoke.
Но к такому Sink трудно добраться, если не заводить базовый класс. Прийдется через Reflection ковырять ContextBoundObject, так как там все закрыто.
Создание своего RealProxy тоже имеет некоторые недостатки: Приходится контролировать весь процесс создания объекта, что не очень просто.
Есть например одна вещь которой не хватает в приведенной процедуре создания proxy, если все делать по правилам.
Не хватает как раз именно построения цепочки IMessageSink на основе атрибутов ContextAttribute. :))
Стандартный proxy это делает.
Иначе все перехватчики на основе ContextAttribute просто отрубаются как будто их и нет. А в цепочке наследования, на одном из классов может оказаться такой атрибут, и игнорировать их тоже не очень хорошо.
Для построения цепочки нужно пройтись по всем атрибутам наследникам ContextAttribute и вызывать у них GetPropertiesForNeContext. Потом из полученых properties вызывать GetObjectSink(obj,next), каждой последующей функции в next передавать предыдущий sink чтобы получился связаный список.
public override IMessage Invoke(IMessage msg)
{
IMessage rm = null;
if (msg is IConstructionCallMessage)
{ ...//строить цепочку где-то здесь
Мне в приведенном коде понравилась такая фича
[AspectStack]
public class TestObject : ContextBoundObject
{
...
TestObject to = new TestObject();
IAspectStack aspectStack = (IAspectStack) to;
Похоже происходит кастинг к члену класса.
Но где, интересно, код который это выполняет, все тщательно обыскал и ничего не похожего не нашел. :???:
Есть только реализация bool CanCastTo(.. , но этого явно недостаточно.
Re[5]: вызов SP из SQL Server на C#, через AOP оболочку
Здравствуйте Silver_s, Вы писали:
SS>Здравствуйте TK, Вы писали:
SS>Выполняющемуся методу конечно не желательно давать возможность подключать,отключать аспекты. SS>Но это вопрос неоднозначный — должен ли класс что-то знать о подключенных аспектах. В COM+ объекты имеют возможность получать информацию из аспектов-контекстов в которых выполняются. И вроде это используется. Напр. метод может запросить выполняется ли он в контексте транзакции и если нет то ругнуться. Это, вобще-то не трудно сделать при необходимости: SS>Через IMessage можно получить LogicalCallContext и туда засунуть объект, который можно извлечь через контекст при выполнении метода.
Тут да... всему свое место... поддержку транзакций проще реализовать используя ContextAttributе, а пул объектов или динамическую поддержку интерфейсов, то это ProxyAttribute
SS>По поводу сравнения двух методов подключения ( ProxyAttribute и ContextAttribute ). SS>----------------------- SS>Тут как всегда.. Через ProxyAttribute более гибкий но более сложный способ так как перехват происходит на более ранней стадии. SS>Через ContextAttribute гораздо проще, но из-за недостаточной гибкости метода приходится извращаться, что в конечном итоге все усложняет.
Ну сложности здесь не больше чем для ContextAttribute
SS>При вызове метода в ContextBoundObject, Proxy получает упрвление первым и через Invoke передает IMessage вызова дальше на MessageSinks. SS>RealProxy.Invoke -> IMessageSink -> IMessageSink -> OriginalMethod SS>Эти IMessageSink как раз и создаются аттрибутами ContextProperty. Здесь ни чего особенного делать не надо, можно сказать атрибут нужен только для того чтобы вернуть IMessageSink перехватчика, цепочка выстраивается автоматически. Но этот встроенный механизм через ContextProperty слишком негибкий, чтобы на него полагаться — для каждого подключения аспекта надо добавлять атрибут, тяжело управлять их созданием и трудно до них потом добраться и.т.д.. Правда выход есть — можно создать один свой Sink и только к нему подключать дополнительные перехватчики. Тогда будет примерно то-же что и с перехватом RealProxy->Invoke.
Вот только это может оказаться не совсем удобно... Атрибут — это статический (практически) признак класса а аспект бывает нужно добавить к конкретному объекту...
SS>Но к такому Sink трудно добраться, если не заводить базовый класс. Прийдется через Reflection ковырять ContextBoundObject, так как там все закрыто.
Ничего там не зарыто. Это что-то типа класса маркера для рантайма.
SS>Создание своего RealProxy тоже имеет некоторые недостатки: Приходится контролировать весь процесс создания объекта, что не очень просто.
Ну и не так уж сложно... В большинстве случаев это даже полезно... (осталось только контролировать процесс создания чужих объектов )
SS>Есть например одна вещь которой не хватает в приведенной процедуре создания proxy, если все делать по правилам. SS>Не хватает как раз именно построения цепочки IMessageSink на основе атрибутов ContextAttribute. SS>Стандартный proxy это делает.
Наверное надо будет его в себя аггрегировать
SS>Иначе все перехватчики на основе ContextAttribute просто отрубаются как будто их и нет. А в цепочке наследования, на одном из классов может оказаться такой атрибут, и игнорировать их тоже не очень хорошо.
Ну, это просто пример... И для промышленного использования его придется доработать
SS>Для построения цепочки нужно пройтись по всем атрибутам наследникам ContextAttribute и вызывать у них GetPropertiesForNeContext. Потом из полученых properties вызывать GetObjectSink(obj,next), каждой последующей функции в next передавать предыдущий sink чтобы получился связаный список.
SS>
SS>public override IMessage Invoke(IMessage msg)
SS>{
SS> IMessage rm = null;
SS> if (msg is IConstructionCallMessage)
SS> { ...//строить цепочку где-то здесь
SS>
Может быть и здесь... Если только это не пул
SS>Мне в приведенном коде понравилась такая фича SS>[AspectStack] SS>public class TestObject : ContextBoundObject SS>{ SS>... SS>TestObject to = new TestObject(); SS>IAspectStack aspectStack = (IAspectStack) to;
SS>Похоже происходит кастинг к члену класса. SS>Но где, интересно, код который это выполняет, все тщательно обыскал и ничего не похожего не нашел. SS>Есть только реализация bool CanCastTo(.. , но этого явно недостаточно.
Этого практически достаточно (нужна еще обработка в RealProxy.Invoke). мало того — можно написать код который будет кастить кого угодно во что угодно, а учитывая, что прокси можно применять для любых объектов унаследованных от MarshlByRefObject, то можно подменить половину стандартной библиотеки и никто и не заметит
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[6]: вызов SP из SQL Server на C#, через AOP оболочку
Здравствуйте TK, Вы писали:
TK>Этого практически достаточно (нужна еще обработка в RealProxy.Invoke). мало того — можно написать код который будет кастить кого угодно во что угодно, а учитывая, что прокси можно применять для любых объектов унаследованных от MarshlByRefObject, то можно подменить половину стандартной библиотеки и никто и не заметит :)
Да, это крутая возможность. Особенно динамическое подключение прокси к уже созданному экземпляру класса. К половине библиотеки так точно можно подключиться. Наследников MarshlByRefObject много.
Реализацию MyProxy привожу ниже. Получилось классная штука, жалко только хреново работает :)
Точнее перехватываются вызовы только первого уровня вложенности, внутри перехваченного метода объект (this) уже не прокси,
а сам объект. Причина в этом:
Здесь передается GetUnwrappedServer()- оригинальный объект, поэтому внутри вызываемого метода он уже не прокси.
А проки передавать нельзя — рекурсия получится и до настоящего метода дело не дойдет.
Вопрос на засыпку: Можно ли что-нибудь придумать, чтобы в функциях любой вложенности объект оставался прокси ???.
public class MyProxy : RealProxy, IRemotingTypeInfo
{
public MyProxy(Type type) : base(type){}
public override IMessage Invoke(IMessage msg)
{
IMethodCallMessage mcm = (IMethodCallMessage) msg;
Console.WriteLine(mcm.MethodName+ " called");
IMessage rm = RemotingServices.ExecuteMessage(GetUnwrappedServer(), mcm);
return rm;
}
public bool CanCastTo(System.Type fromType, object o)
{
return fromType.IsInstanceOfType(GetUnwrappedServer());
}
public string TypeName
{
get{return GetProxiedType().ToString();}
set{throw new NotSupportedException();}
}
public MarshalByRefObject Attach(MarshalByRefObject obj)
{
AttachServer(obj);
return (MarshalByRefObject)GetTransparentProxy();
}
public static MarshalByRefObject Wrap(MarshalByRefObject obj)
{
return new MyProxy(obj.GetType()).Attach(obj);
}
}
class Cl: MarshalByRefObject
{
public int Func(int i){return Func2(i);}
public int Func2(int i){return i+1;}
}
class Class1
{
static void Main()
{
Cl m1=new Cl();
Cl m2=(Cl)MyProxy.Wrap(m1);
// При вызове выодится в консоль(перехватывается) только вызов Func(),
// вложенная Func2 не не перехватывается т.к. внутри Func() объект уже не прокси.
m2.Func(5);
Console.ReadLine();
}
}
Re[7]: вызов SP из SQL Server на C#, через AOP оболочку
SS>перехватываются вызовы только первого уровня вложенности, внутри перехваченного метода объект (this) уже не прокси, SS>а сам объект. SS>Вопрос на засыпку: Можно ли что-нибудь придумать, чтобы в функциях любой вложенности объект оставался прокси ???.
Вобще-то для остальных методов подключения маршализоваться между вложенными методами одного объекта тоже невозможно. Может и не нужно.
А так довольно заманчиво отказаться от ContextBoundObject. Думаю имеет смысл реализовывать перехватчики таким образом.
Re[8]: вызов SP из SQL Server на C#, через AOP оболочку
Здравствуйте Silver_s, Вы писали:
SS>Здравствуйте Silver_s, Вы писали:
SS>>перехватываются вызовы только первого уровня вложенности, внутри перехваченного метода объект (this) уже не прокси, SS>>а сам объект. SS>>Вопрос на засыпку: Можно ли что-нибудь придумать, чтобы в функциях любой вложенности объект оставался прокси ???.
Вряд-ли такое выйдет...(если только с IL не мудрить) Все-таки this должен где-то существовать...
SS>Вобще-то для остальных методов подключения маршализоваться между вложенными методами одного объекта тоже невозможно. Может и не нужно. SS>А так довольно заманчиво отказаться от ContextBoundObject. Думаю имеет смысл реализовывать перехватчики таким образом.
Ага, еще можно сделать пародию на множественное наследование
Ну или хотя-бы более/менее приличную аггрегацию...
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.