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

}
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.