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就无法识别并调用。
修复方案
- 在子类
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(); } }
- 给基类
BitwiseAggregate添加[Serializable]特性,因为SQL Server的CLR聚合要求类必须可序列化:
[Serializable] public class BitwiseAggregate : IBinarySerialize { // 基类原有代码不变 }
修改后,SQL Server就能识别到子类的Init和Terminate方法,并正确调用基类的实现逻辑,聚合函数即可正常工作。
内容的提问来源于stack exchange,提问作者Ben Thul
相关产品推荐
相关产品推荐

