SRC: Вызов SP из SQL Server на C#, через AOP оболочку
От: Silver_s Ниоткуда  
Дата: 15.08.02 14:45
Оценка: 52 (4)
Для упрощения процесса вызова сохраненных процедур из 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 set
select * from  ...
    
-- второй result set
select * 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 Server
        object 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 в трех файлах.




//---------------------------------------
//---------------------------------------
//Файл1


using System;
using System.Data;
using System.Data.SqlClient;
using System.Collections;
using System.Reflection;
using Aspect;

namespace SQLSRVAspect
{


    [CallSP]
    public class SQLSrvSPClass : ContextBoundObject
    {
        public SqlConnection Connection=null;
        private SQLSrvSPClass(){}
        protected SQLSrvSPClass(SqlConnection conn)
        {
            Connection=conn;
        }
    }

        //-----------------------------------
    [AttributeUsage(AttributeTargets.Class)]
    class CallSPAttribute : AspectAttribute
    {
        public override Type GetAspectType()
        {
            return typeof(SQLSrvAspect);
        }
    }
    //-----------------------------------

    class SQLSrvAspect : AspectBaseClass
    {
        public SqlConnection Connection=null;
        private SqlCommand _Command;

        protected override 
            void Init(MarshalByRefObject obj)
        {
            this.Connection=((SQLSrvSPClass)obj).Connection;
        }

        public override  
            void PreProcess (string MethodName,MethodInfo minf,Argument[] args)
        {
            _Command=GenerateCommand(Connection,MethodName,args);
        }
        public override  
            void PostProcess(string MethodName,MethodInfo minf,Argument[] args,Argument ReturnVal)
        {
            try
            {
                if(ReturnVal._Type==typeof(void))
                {
                    _Command.ExecuteNonQuery();
                }
                else if(ReturnVal._Type==typeof(SqlDataReader))
                {
                    ReturnVal._Value=_Command.ExecuteReader(CommandBehavior.KeyInfo);
                }
                else if(ReturnVal._Type==typeof(DataSet))
                {
                    DataSet ds=new DataSet(MethodName);
                    SqlDataAdapter ad=new SqlDataAdapter(_Command);
                    ad.Fill(ds);
                    ReturnVal._Value=ds;
                }
                else if(ReturnVal._Type==typeof(DataTable))
                {
                    DataTable dt=new DataTable(MethodName);
                    SqlDataAdapter ad=new SqlDataAdapter(_Command);
                    ad.Fill(dt);
                    ReturnVal._Value=dt;

                }
                else
                {
                    string s=string.Format(@"Method ""{0}"" returns unsupported type ""{1}"" ",MethodName,ReturnVal._Type.Name);
                    throw new Exception(s);
                }
            }
            catch(SqlException ex)
            {
                throw new Exception(ex.Message+"   :at Procedure \""+MethodName+"\"" ,ex);
            }

            SqlParameterCollection pars=_Command.Parameters;
            for(int i=0;i<pars.Count;i++)
            {
                if(
                    args[i]._Modifier==Modifier.Out || 
                    args[i]._Modifier==Modifier.Ref
                    )
                {
                    SqlParameter par=pars["@"+args[i]._Name];
                    if(par.Value!=null)
                        args[i]._Value=par.Value;
                }
            }
        }

        public static SqlCommand GenerateCommand(SqlConnection connection,string ProcName,Argument[] args)
        {
            SqlCommand command = new SqlCommand(ProcName, connection);
            command.CommandType = CommandType.StoredProcedure;

            for(int i=0;i<args.Length;i++)
            {
                Argument arg=args[i];
                SqlParameter sqlParameter = new SqlParameter();
                sqlParameter.ParameterName = "@" + arg._Name;
                sqlParameter.Value = arg._Value;
                if(arg._Name=="ReturnValue")
                {
                    if(i!=args.Length-1)
                        throw new Exception("ReturnValue argument should be last");
                    if(arg._Modifier==Modifier.None)
                        throw new Exception("ReturnValue argument should be ref or out");
                    sqlParameter.Value=null;
                    sqlParameter.Direction=ParameterDirection.ReturnValue;
                }
                else if(arg._Modifier==Modifier.Ref)
                    sqlParameter.Direction=ParameterDirection.InputOutput;
                else if(arg._Modifier==Modifier.Out)
                    sqlParameter.Direction=ParameterDirection.Output;

                command.Parameters.Add(sqlParameter);
            }
            return command;
        }

    }

}


//---------------------------------------------------
//---------------------------------------------------
// Файл AspectAttribute.cs


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;} }
        }
    }

}



//---------------------------------------------------
//---------------------------------------------------
// Файл AspectBase.cs


using System;
using System.Runtime.Remoting.Messaging;
using System.Reflection;

namespace Aspect
{
    public enum Modifier
    {
        None=0,Ref,Out
    }
    public class Argument
    {
        public string _Name=null;
        public Type   _Type=null;
        public Modifier _Modifier=Modifier.None;
        public object  _Value=null;
    }

    abstract public class AspectBaseClass : IMessageSink 
    {
        private IMessageSink m_next;
        const string ContextName="MyCtxName";
    
        public virtual void InitBase(IMessageSink next,MarshalByRefObject obj)
        {

            m_next = next;
            Init(obj);
        }
        abstract protected void Init(MarshalByRefObject obj);
        abstract public void PreProcess (string MethodName,MethodInfo minf,Argument[] args);
        abstract public void PostProcess(string MethodName,MethodInfo minf,Argument[] args,Argument returnVal);

        private static Argument[] ExtractArguments(IMethodMessage msg,out MethodInfo outMinf)
        {
            if(msg==null)
                throw new Exception("Error in method call");
            string tn=msg.TypeName;
            Type t=msg.MethodBase.DeclaringType;
            //Type t = Type.GetType(tn);
            MemberInfo[] minfAr=t.GetMember(msg.MethodName);
            if(minfAr==null || minfAr.Length!=1)
                throw new Exception("Should be only one member");
            MethodInfo minf=(MethodInfo)minfAr[0];
        
            ParameterInfo[] paramsAr=minf.GetParameters();
            int paramCount=paramsAr.Length;

            Argument[] args=new Argument[paramCount];
            for(int i=0;i<paramCount;i++)
            {
                args[i]=new Argument();
                args[i]._Name=paramsAr[i].Name;
                args[i]._Type=paramsAr[i].ParameterType;
                args[i]._Value=msg.Args[i];
                if(paramsAr[i].ParameterType.IsByRef)
                    args[i]._Modifier=Modifier.Ref;
                if(paramsAr[i].IsOut)
                    args[i]._Modifier=Modifier.Out;

            }
            outMinf=minf;
            return args;
        }

        public IMessage SyncProcessMessage(IMessage msg) 
        {
            IMethodMessage meth = msg as IMethodMessage;
            if(meth==null)
                throw new Exception("Error in method call");
            MethodInfo minf;
            Argument[] args=ExtractArguments(meth,out minf);
            PreProcess(meth.MethodName,minf,args);
            IMethodMessage retMsg = (IMethodMessage)m_next.SyncProcessMessage(msg);
            Argument[] retArgs=ExtractArguments(retMsg,out minf);

            Argument returnArgument=new Argument();
            returnArgument._Value=((IMethodReturnMessage)retMsg).ReturnValue;
            returnArgument._Type=minf.ReturnType;

            PostProcess(meth.MethodName,minf,retArgs,returnArgument);

            object[] argVals=new object[retArgs.Length];
            for(int i=0;i<argVals.Length;i++)
                argVals[i]=retArgs[i]._Value;


        
            ReturnMessage rmes=new ReturnMessage(returnArgument._Value,argVals,argVals.Length,retMsg.LogicalCallContext,(IMethodCallMessage)msg);

            return rmes;
        }
        public IMessageSink NextSink
        {
            get
            {
                return m_next;
            }
        }

        public IMessageCtrl AsyncProcessMessage(IMessage msg,IMessageSink replySink)
        {
            return null;
        }
    
    }

}
Re: вызов SP из SQL Server на C#, через AOP оболочку
От: IT Россия linq2db.com
Дата: 15.08.02 15:50
Оценка:
Здравствуйте Silver_s, Вы писали:

SS>Смысл следующий. Для вызова сохраненных процедур из SQL сервер надо создать класс производный от SQLSrvSPClass. Все функции добавленные в этот класс при вызове будут реально вызывать сохраненые процедуры на SQL Server, с таким же именем и аргументами как и имя функции в классе.


Круто. Хорошо бы об этом статейку написать
Если нам не помогут, то мы тоже никого не пощадим.
Re[2]: вызов SP из SQL Server на C#, через AOP оболочку
От: TK Лес кывт.рф
Дата: 15.08.02 19:07
Оценка:
Здравствуйте IT, Вы писали:

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


SS>>Смысл следующий. Для вызова сохраненных процедур из SQL сервер надо создать класс производный от SQLSrvSPClass. Все функции добавленные в этот класс при вызове будут реально вызывать сохраненые процедуры на SQL Server, с таким же именем и аргументами как и имя функции в классе.


IT>Круто. Хорошо бы об этом статейку написать


Ага можно будет сравнить два варианта подхода к подобным задачам (предложенный здесь и с использованием CustomProxy)
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re: вызов SP из SQL Server на C#, через AOP оболочку
От: Юнусов Булат Россия  
Дата: 16.08.02 11:41
Оценка: 14 (1)
Мб все это видели, есть еще такое:
Data Access Application Block


вкраце, что это
Re[2]: вызов SP из SQL Server на C#, через AOP оболочку
От: IT Россия linq2db.com
Дата: 16.08.02 13:53
Оценка:
Здравствуйте Юнусов Булат, Вы писали:

ЮБ>Мб все это видели, есть еще такое:


Это не совсем в тему, но всё равно спасибо за ссылочку.
Если нам не помогут, то мы тоже никого не пощадим.
Re[2]: вызов SP из SQL Server на C#, через AOP оболочку
От: Silver_s Ниоткуда  
Дата: 16.08.02 14:38
Оценка: 22 (1)
Здравствуйте IT, Вы писали:
IT>Круто. Хорошо бы об этом статейку написать

Насчет статьи не знаю, надо подумать получится ли что интересное.

Пока только немного прокоментирую код.

Идею я почерпнул из этой статейки.
Aspect-Oriented Programming Enables Better Code Encapsulation and Reuse
http://msdn.microsoft.com/msdnmag/issues/02/03/AOP/AOP.asp

На базе этого реализовал два класса 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 функции:

class MyAspect: AspectBaseClass
{
  ... override void Init(MarshalByRefObject obj)...
  ... override void PreProcess(string MethodName,MethodInfo minf,Argument[] args)...
  ... override void PostProcess(string MethodName,MethodInfo minf,Argument[] args,Argument returnVal)...
}


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 оболочку
От: TK Лес кывт.рф
Дата: 18.08.02 05:10
Оценка:
Здравствуйте Silver_s, Вы писали:


Кстати — вот тоже статья на тему вызова процедур, аттрибутов и т.п.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[2]: вызов SP из SQL Server на C#, через AOP оболочку
От: Silver_s Ниоткуда  
Дата: 18.08.02 12:34
Оценка:
Здравствуйте 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 оболочку
От: TK Лес кывт.рф
Дата: 18.08.02 15:03
Оценка: 21 (2)
Здравствуйте 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;
        }
    }


Тест:
        static void Main(string[] args)
        {
            TestObject to = new RSDN.Sample.TestObject();

            IAspectStack aspectStack = (IAspectStack) to;

            aspectStack.Preprocess += new AspectDelegate(Preprocess);
            aspectStack.Postprocess += new AspectDelegate(Postprocess);

            to.TestMethod("Request");
        }

        static void Preprocess(object sender, IMessage msg)
        {
            IMethodCallMessage mcm = (IMethodCallMessage) msg;
            
            Console.WriteLine("Preprocess: {0}", mcm.MethodName);
        }

        static void Postprocess(object sender, IMessage msg)
        {
            IMethodReturnMessage mrm = (IMethodReturnMessage) msg;

            Console.WriteLine("Postprocess: {0}", mrm.MethodName);
        }


Аттрибуты:
    public delegate void AspectDelegate(object sender, IMessage msg);

    public interface IAspectStack 
    {
        event AspectDelegate Preprocess;
        event AspectDelegate Postprocess;        
    }
    public class AspectStackProxy : RealProxy, IRemotingTypeInfo
    {
        private MarshalByRefObject _serverObject;
        private AspectStack _aspectStack;
        
        #region AspectStack Class 
        internal class AspectStack : IAspectStack
        {
            private event AspectDelegate _preprocess;
            private event AspectDelegate _postprocess;

            public event AspectDelegate Preprocess
            {
                add 
                {
                    _preprocess += value;
                }
                remove
                {
                    _preprocess -= value;
                }
            }
            
            public event AspectDelegate Postprocess
            {
                add 
                {
                    _postprocess += value;
                }
                remove
                {
                    _postprocess -= value;
                }            
            }


            public void OnPreprocess(object sender, IMessage msg) 
            {
                if (_preprocess != null) 
                    _preprocess(sender, msg);
            }

            public void OnPostprocess(object sender, IMessage msg)
            {
                if (_postprocess != null) 
                    _postprocess(sender, msg);
            }
        }
        #endregion

        public AspectStackProxy(Type type) : base(type)
        {
            _aspectStack = new AspectStack();
            _serverObject = null;
        }

        public override IMessage Invoke(IMessage msg)
        {        
            IMessage rm = null;
            if (msg is IConstructionCallMessage)
            {
                IConstructionCallMessage ctorMsg = (IConstructionCallMessage) msg;
                
                AspectStackProxy ap = new AspectStackProxy(GetProxiedType());
                ap.InitializeServerObject(ctorMsg);
                _serverObject = ap.GetUnwrappedServer();            

                rm = EnterpriseServicesHelper.CreateConstructionReturnMessage(ctorMsg, (MarshalByRefObject)GetTransparentProxy());            
            }
            else
            {
                IMethodCallMessage mcm = (IMethodCallMessage) msg;
                    
                if (mcm.TypeName == typeof (IAspectStack).AssemblyQualifiedName)
                {
                    mcm.MethodBase.Invoke(_aspectStack, mcm.InArgs);
                    rm = new ReturnMessage(null, null, 0, null, mcm);
                }
                else
                {
                    _aspectStack.OnPreprocess(_serverObject, msg);
                    rm = RemotingServices.ExecuteMessage(_serverObject, mcm);
                    _aspectStack.OnPostprocess(_serverObject, rm);
                }
            }

            return rm;
        }

        #region Implementation of IRemotingTypeInfo
        public bool CanCastTo(System.Type fromType, object o)
        {
            return fromType.IsInstanceOfType(_serverObject) || fromType == typeof (IAspectStack);     
        }

        public string TypeName
        {
            get
            {
                return GetProxiedType().ToString();
            }
            
            set
            {
                throw new NotSupportedException();
            }
        }
        #endregion
    }

    [AttributeUsage(AttributeTargets.Class)]
    public class AspectStackAttribute : ProxyAttribute
    {
        public override MarshalByRefObject CreateInstance(System.Type serverType)
        {        
            AspectStackProxy sp = new AspectStackProxy(serverType);
            return (MarshalByRefObject) sp.GetTransparentProxy();
        }
    }
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[4]: вызов SP из SQL Server на C#, через AOP оболочку
От: Silver_s Ниоткуда  
Дата: 19.08.02 09:09
Оценка:
Здравствуйте 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 оболочку
От: TK Лес кывт.рф
Дата: 19.08.02 09:30
Оценка: 6 (1)
Здравствуйте 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 оболочку
От: Silver_s Ниоткуда  
Дата: 19.08.02 16:22
Оценка:
Здравствуйте TK, Вы писали:

TK>Этого практически достаточно (нужна еще обработка в RealProxy.Invoke). мало того — можно написать код который будет кастить кого угодно во что угодно, а учитывая, что прокси можно применять для любых объектов унаследованных от MarshlByRefObject, то можно подменить половину стандартной библиотеки и никто и не заметит :)


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

Например делаем так:

static void Main() 
{
    Application.Run( (Form)MyProxy.Wrap(new Form1()) );
}

И можно трассировать все вызовы методов в форме.

Реализацию MyProxy привожу ниже. Получилось классная штука, жалко только хреново работает :)
Точнее перехватываются вызовы только первого уровня вложенности, внутри перехваченного метода объект (this) уже не прокси,
а сам объект. Причина в этом:


... Invoke(IMessage msg)
{
 ... RemotingServices.ExecuteMessage(GetUnwrappedServer(), mcm);

Здесь передается 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 оболочку
От: Silver_s Ниоткуда  
Дата: 19.08.02 17:15
Оценка:
Здравствуйте Silver_s, Вы писали:


SS>перехватываются вызовы только первого уровня вложенности, внутри перехваченного метода объект (this) уже не прокси,

SS>а сам объект.
SS>Вопрос на засыпку: Можно ли что-нибудь придумать, чтобы в функциях любой вложенности объект оставался прокси ???.


Вобще-то для остальных методов подключения маршализоваться между вложенными методами одного объекта тоже невозможно. Может и не нужно.
А так довольно заманчиво отказаться от ContextBoundObject. Думаю имеет смысл реализовывать перехватчики таким образом.
Re[8]: вызов SP из SQL Server на C#, через AOP оболочку
От: TK Лес кывт.рф
Дата: 19.08.02 18:18
Оценка:
Здравствуйте Silver_s, Вы писали:

SS>Здравствуйте Silver_s, Вы писали:



SS>>перехватываются вызовы только первого уровня вложенности, внутри перехваченного метода объект (this) уже не прокси,

SS>>а сам объект.
SS>>Вопрос на засыпку: Можно ли что-нибудь придумать, чтобы в функциях любой вложенности объект оставался прокси ???.

Вряд-ли такое выйдет...(если только с IL не мудрить) Все-таки this должен где-то существовать...

SS>Вобще-то для остальных методов подключения маршализоваться между вложенными методами одного объекта тоже невозможно. Может и не нужно.

SS>А так довольно заманчиво отказаться от ContextBoundObject. Думаю имеет смысл реализовывать перехватчики таким образом.

Ага, еще можно сделать пародию на множественное наследование
Ну или хотя-бы более/менее приличную аггрегацию...
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.