You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SqlUserDefinedAggregate能否作为派生类?继承实现为何失效?

SQL Server自定义位运算聚合函数基类提取后Init与Terminate方法失效

问题背景

原本已实现可正常工作的按位或SQL CLR聚合函数,为复用通用逻辑、快速实现按位与聚合函数,将Init、Terminate以及序列化相关代码提取到基类BitwiseAggregate中,但修改后发现基类中的Init()和Terminate()方法完全不生效,聚合函数无法正常运行。

原可工作的按位或聚合函数代码

using Microsoft.SqlServer.Server;
using System;
using System.Collections;
using System.Data.SqlTypes;
using System.IO;

namespace BitwiseAggregates
{
    [Serializable]
    [SqlUserDefinedAggregate(
        Format.UserDefined,
        MaxByteSize = 8000,
        IsInvariantToNulls = false,
        IsInvariantToDuplicates = true,
        IsInvariantToOrder = true
    )]
    public class BitwiseOrAggregate : IBinarySerialize
    {
        private BitArray Accumulator { get; set; }
        private bool IsFirstValue { get; set; }
        private bool IsNull { get; set; }

        public void Init()
        {
            IsFirstValue = true;
            IsNull = false;
        }
        public void Accumulate(SqlBinary Values)
        {
            if (Values.IsNull == false && this.IsNull == false)
            {
                if (this.IsFirstValue == true)
                {
                    this.Accumulator = FromSqlBinary(Values);
                    this.IsFirstValue = false;
                }
                else
                {
                    BitArray tempBA = FromSqlBinary(Values);
                    this.Accumulator = this.Accumulator.Or(tempBA);
                }
            }
            else
            {
                IsNull = true;
            }
        }
        public void Merge(BitwiseOrAggregate that)
        {
            if (this.Accumulator.Length != that.Accumulator.Length)
            {
                throw new InvalidOperationException("All values must be the same length");
            }

            if (that.IsNull)
            {
                this.IsNull = true;
            }
            else
            {
                this.Accumulator = this.Accumulator.Or(that.Accumulator);
            }
        }
        public SqlBinary Terminate()
        {
            if (IsNull == true)
            {
                return SqlBinary.Null;
            }
            else
            {
                return new SqlBinary(FromBitArray(Accumulator));
            }
        }

        // Methods for IBinarySerialize
        public void Read(BinaryReader r)
        {
            IsFirstValue = r.ReadBoolean();
            IsNull = r.ReadBoolean();

            if (IsNull == false)
            {
                int bytesToRead = r.ReadInt32();

                byte[] ba = r.ReadBytes(bytesToRead);

                Accumulator = new BitArray(ba);
            }
        }
        public void Write(BinaryWriter w)
        {
            w.Write(IsFirstValue);
            w.Write(IsNull);

            if (IsNull == false)
            {
                byte[] ba = FromBitArray(Accumulator);

                int bytesToWrite = ba.Length;
                w.Write(bytesToWrite);

                w.Write(ba);
            }
        }

        // Helper methods
        protected BitArray FromSqlBinary(SqlBinary bytes)
        {
            return new BitArray((byte[])bytes);
        }
        protected byte[] FromBitArray(BitArray ba)
        {
            int returnLength = ba.Length / 8;
            byte[] retVal = new byte[returnLength];

            ba.CopyTo(retVal, 0);
            return retVal;
        }
    }
}

提取基类后的代码(存在问题)

using Microsoft.SqlServer.Server;
using System;
using System.Collections;
using System.Data.SqlTypes;
using System.IO;

namespace BitwiseAggregates
{
    [Serializable]
    [SqlUserDefinedAggregate(
        Format.UserDefined,
        MaxByteSize = 8000,
        IsInvariantToNulls = false,
        IsInvariantToDuplicates = true,
        IsInvariantToOrder = true
    )]
    public class BitwiseOrAggregate : BitwiseAggregate
    {
        public void Accumulate(SqlBinary Values)
        {
            if (Values.IsNull == false && this.IsNull == false)
            {
                if (this.IsFirstValue == true)
                {
                    this.Accumulator = FromSqlBinary(Values);
                    this.IsFirstValue = false;
                }
                else
                {
                    BitArray tempBA = FromSqlBinary(Values);
                    this.Accumulator = this.Accumulator.Or(tempBA);
                }
            }
            else
            {
                IsNull = true;
            }
        }
        public void Merge(BitwiseOrAggregate that)
        {
            if (this.Accumulator.Length != that.Accumulator.Length)
            {
                throw new InvalidOperationException("All values must be the same length");
            }

            if (that.IsNull)
            {
                this.IsNull = true;
            }
            else
            {
                this.Accumulator = this.Accumulator.Or(that.Accumulator);
            }
        }
    }

    public class BitwiseAggregate : IBinarySerialize
    {
        protected BitArray Accumulator { get; set; }
        protected bool IsFirstValue { get; set; }
        protected bool IsNull { get; set; }
        public void Init()
        {
            IsFirstValue = true;
            IsNull = false;
        }

        public SqlBinary Terminate()
        {
            if (IsNull == true)
            {
                return SqlBinary.Null;
            }
            else
            {
                return new SqlBinary(FromBitArray(Accumulator));
            }
        }

        // Methods for IBinarySerialize
        public void Read(BinaryReader r)
        {
            IsFirstValue = r.ReadBoolean();
            IsNull = r.ReadBoolean();

            if (IsNull == false)
            {
                int bytesToRead = r.ReadInt32();

                byte[] ba = r.ReadBytes(bytesToRead);

                Accumulator = new BitArray(ba);
            }
        }
        public void Write(BinaryWriter w)
        {
            w.Write(IsFirstValue);
            w.Write(IsNull);

            if (IsNull == false)
            {
                byte[] ba = FromBitArray(Accumulator);

                int bytesToWrite = ba.Length;
                w.Write(bytesToWrite);

                w.Write(ba);
            }
        }

        // Helper methods
        protected BitArray FromSqlBinary(SqlBinary bytes)
        {
            return new BitArray((byte[])bytes);
        }
        protected byte[] FromBitArray(BitArray ba)
        {
            int returnLength = ba.Length / 8;
            byte[] retVal = new byte[returnLength];

            ba.CopyTo(retVal, 0);
            return retVal;
        }
    }
}

问题原因与解决方案

核心原因

SQL Server的CLR聚合函数宿主只会检查标记了[SqlUserDefinedAggregate]特性的类本身,是否包含聚合所需的全部方法:Init、Accumulate、Merge、Terminate。它不会自动从基类继承这些方法的签名,即使基类已经实现了这些方法,只要子类没有显式暴露它们,SQL Server就无法识别并调用。

修复方案

  1. 在子类BitwiseOrAggregate中显式实现Init和Terminate方法,直接调用基类的对应方法:
[Serializable]
[SqlUserDefinedAggregate(
    Format.UserDefined,
    MaxByteSize = 8000,
    IsInvariantToNulls = false,
    IsInvariantToDuplicates = true,
    IsInvariantToOrder = true
)]
public class BitwiseOrAggregate : BitwiseAggregate
{
    // 显式实现Init,调用基类方法
    public new void Init()
    {
        base.Init();
    }

    // 原有Accumulate方法不变
    public void Accumulate(SqlBinary Values)
    {
        if (Values.IsNull == false && this.IsNull == false)
        {
            if (this.IsFirstValue == true)
            {
                this.Accumulator = FromSqlBinary(Values);
                this.IsFirstValue = false;
            }
            else
            {
                BitArray tempBA = FromSqlBinary(Values);
                this.Accumulator = this.Accumulator.Or(tempBA);
            }
        }
        else
        {
            IsNull = true;
        }
    }

    // 原有Merge方法不变
    public void Merge(BitwiseOrAggregate that)
    {
        if (this.Accumulator.Length != that.Accumulator.Length)
        {
            throw new InvalidOperationException("All values must be the same length");
        }

        if (that.IsNull)
        {
            this.IsNull = true;
        }
        else
        {
            this.Accumulator = this.Accumulator.Or(that.Accumulator);
        }
    }

    // 显式实现Terminate,调用基类方法
    public new SqlBinary Terminate()
    {
        return base.Terminate();
    }
}
  1. 给基类BitwiseAggregate添加[Serializable]特性,因为SQL Server的CLR聚合要求类必须可序列化:
[Serializable]
public class BitwiseAggregate : IBinarySerialize
{
    // 基类原有代码不变
}

修改后,SQL Server就能识别到子类的Init和Terminate方法,并正确调用基类的实现逻辑,聚合函数即可正常工作。


内容的提问来源于stack exchange,提问作者Ben Thul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 22:30:49