none
【SQL CLR】自定义聚合函数,如何让Accumulate方法向Terminate方法传值 RRS feed

  • 问题

  • 不知道中文在这里好不好使~Anyway:

    我在用C#写一个SQL聚合函数JoinString,作用是合并字符串,功能类似于string.Join(),例如有个表是这样:

    Col

    -------

    aa

    bb

    cc

    执行select dbo.JoinString(Col,'|@|') from tb后,返回aa|@|bb|@|cc,用户定义的分隔符只能在Accumulate()方法中获取,获取到该分隔符以后,我需要在Terminate方法中获取该分隔符的长度,以便将末尾的分隔符移除后再返回,但问题来了,请看:

    public void Accumulate(SqlString value, SqlString separater, SqlBoolean removeEmpty)
    {
    septrLength = separater.Value.Length;
    //DoSomething
    }

    public SqlString Terminate()
    {
    if (sb.Length != 0) { sb.Length -= septrLength; }
    return new SqlString(sb.ToString());
    }

    如上,我想通过一个类字段septrLength来存储分隔符的长度,但实践证明,Terminate()方法中获取到的septrLength永远是0,我怀疑是因为该结构体在一次聚合中被多次实例化,所以尝试改为静态字段,但这样以后又部署不了,应该是不允许有静态字段,TNND为SQL作CLR开发真是限制多多。
    求大家支招。

    2013年1月10日 4:41

答案

  • 我想我知道原因了,聚合函数类在一次调用不是被多次实例化,而是被多次序列化和反序列化,这也是为什么SqlUserDefinedAggregate的Format不是Native就是UserDefined的原因,当然还有一种Unknow目前还不能理解,总之我推测聚合函数是通过序列化/反序列化来保存状态,选择Native的话可以认为是CLR自动帮我们实现序/反序,但限制是类中不能使用任何引用类型作为成员,如果要使用,则必须用UserDefined方式,也就是自己实现序/反序,那么当自己实现时,就必须在序列化时把该保存的状态进行保存,比如是通过实现IBinarySerialize来进行序/反序时,就必须把所有东东写入BinaryWriter的基础流中,完了在反序列化时,又从BinaryReader的基础流中取出状态,并赋值给对象。

    不知道我理解的对不对,反正按这个设想改造代码后,就可以在Terminate方法中取到正确的值了,问题解决。大体例子如下:

            public void Read(BinaryReader r)
            {
                bf = new BinaryFormatter();
                object[] objs = bf.Deserialize(r.BaseStream) as object[];
                items = objs[0] as StringCollection;
                sptr = objs[1] as string;
            }

            public void Write(BinaryWriter w)
            {
                bf.Serialize(w.BaseStream, new object[] { items, sptr });
            }

    Write就是序列化时执行的方法,其中用BinaryFormatter将要保存的对象写入流,完了在Read(反序列化时)方法中还原对象。

    • 已标记为答案 ahdung_AI 2015年3月26日 11:14
    2015年3月26日 11:13

全部回复

  • This is English fourm. Please post your query in english.

    Regards,
    Rohit Garg
    (My Blog)
    This posting is provided with no warranties and confers no rights.
    Please remember to click Mark as Answer and Vote as Helpful on posts that help you. This can be beneficial to other community members reading the thread.

    2013年1月11日 12:18
  • 用静态字段的话,必须部署为Safe的Assembly。


    想不想时已是想,不如不想都不想。

    2013年1月11日 14:32
    版主
  • 已经是safe的
    2013年1月13日 13:19
  • Sorry, unsafe.

    想不想时已是想,不如不想都不想。

    2013年1月14日 15:25
    版主
  • 实测unsafe一样部署不了,只要把静态字段去掉,立马部署成功。这问题蛋疼了
    2013年1月15日 11:27
  • 你怎么写的?

     public partial class UserDefinedFunctions
        {

    private static string septrLength;

    static UserDefinedFunctions() {
    septrLength = 1;
            }

     [SqlFunction]

    public static int ReturnseptrLength(){

    return septrLength

    }

    }

    这个示例是在构造函数里面给静态字段赋值,所以Safe就可以了。如果在其它函数/方法里面赋值的话,需要Unsafe。


    想不想时已是想,不如不想都不想。

    2013年1月16日 2:02
    版主
  • 是在Accumulate()方法中赋值,即“其它地方”,但是选成unsafe仍然部署不了,操蛋的是只有【部署已失败】几个字,没更多反馈。

    2013年1月17日 1:45
  • 你怎么部署的?

    想不想时已是想,不如不想都不想。

    2013年1月17日 5:15
    版主
  • VS项目菜单里的部署
    2013年1月30日 6:44
  • 为了在 SQL Server 中创建 EXTERNAL_ACCESSUNSAFE
    程序集,必须满足以下两个条件:


    1. 程序集经过了强名称签名或使用证书进行了 Authenticode 签名。此强名称(或证书)在 SQL Server
      内部作为非对称密钥(或证书)创建,它具有对应的带有 EXTERNAL ACCESS ASSEMBLY 权限(对于外部访问程序集)或
      UNSAFE ASSEMBLY 权限(对于不安全的程序集)的登录名。
    2. 数据库所有者 (DBO) 具有 EXTERNAL ACCESS ASSEMBLY(对于 EXTERNAL ACCESS
      程序集)或 UNSAFE ASSEMBLY(对于 UNSAFE 程序集)权限,并且数据库已将 <mshelp:link filterstring="(("ProductVers"="kbsqlserv105") OR ("DocSet"="NETFramework"))" keywords="64b2a53d-4416-4a19-acc0-664a61b45348" tabindex="0">TRUSTWORTHY
      数据库属性</mshelp:link>设置为 ON

    在加载程序集(包括执行)时,也将检查上面所列的两个条件。至少必须满足这些条件之一才能加载程序集。

    我们建议,不要仅仅为了在服务器进程中运行公共语言运行时 (CLR) 代码而将数据库的 <mshelp:link filterstring="(("ProductVers"="kbsqlserv105") OR ("DocSet"="NETFramework"))" keywords="64b2a53d-4416-4a19-acc0-664a61b45348" tabindex="0">TRUSTWORTHY
    数据库属性</mshelp:link>设置为 ON。而是建议在 master
    数据库中通过程序集文件创建非对称密钥。然后,必须创建映射到此非对称密钥的登录名,并且必须向此登录名授予 EXTERNAL ACCESS
    ASSEMBLY
    UNSAFE ASSEMBLY 权限。


    想不想时已是想,不如不想都不想。

    2013年1月30日 8:30
    版主
  • 问题不是unsafe部署不了,是有静态字段就部署不了,如果没有静态字段,unsafe一样可以部署
    2013年2月1日 2:09
  • 那就奇怪了,我用静态字段很多年了。你把手动部署的错误信息贴上来看看。

    想不想时已是想,不如不想都不想。

    2013年2月1日 2:42
    版主
  • 惭愧,等我学学手动部署先
    2013年2月5日 1:42
  • 我想我知道原因了,聚合函数类在一次调用不是被多次实例化,而是被多次序列化和反序列化,这也是为什么SqlUserDefinedAggregate的Format不是Native就是UserDefined的原因,当然还有一种Unknow目前还不能理解,总之我推测聚合函数是通过序列化/反序列化来保存状态,选择Native的话可以认为是CLR自动帮我们实现序/反序,但限制是类中不能使用任何引用类型作为成员,如果要使用,则必须用UserDefined方式,也就是自己实现序/反序,那么当自己实现时,就必须在序列化时把该保存的状态进行保存,比如是通过实现IBinarySerialize来进行序/反序时,就必须把所有东东写入BinaryWriter的基础流中,完了在反序列化时,又从BinaryReader的基础流中取出状态,并赋值给对象。

    不知道我理解的对不对,反正按这个设想改造代码后,就可以在Terminate方法中取到正确的值了,问题解决。大体例子如下:

            public void Read(BinaryReader r)
            {
                bf = new BinaryFormatter();
                object[] objs = bf.Deserialize(r.BaseStream) as object[];
                items = objs[0] as StringCollection;
                sptr = objs[1] as string;
            }

            public void Write(BinaryWriter w)
            {
                bf.Serialize(w.BaseStream, new object[] { items, sptr });
            }

    Write就是序列化时执行的方法,其中用BinaryFormatter将要保存的对象写入流,完了在Read(反序列化时)方法中还原对象。

    • 已标记为答案 ahdung_AI 2015年3月26日 11:14
    2015年3月26日 11:13