简体   繁体   中英

Can a SQL Server user defined CLR type be initialized by any type other than String?

I have created a CLR user defined type as a struct in C# and I would like to be able to initialize this type directly from an int in SQL Server. The following example works for the string and null values, but fails for the int, with the message: Operand type clash: int is incompatible with ExhaustiveInt .

CREATE TABLE ExhaustiveTest (
    v ExhaustiveInt not null
);
insert into ExhaustiveTest 
values ('100'), (null), (100);

I have tried the following in the struct:

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

[Serializable]  
[Microsoft.SqlServer.Server.SqlUserDefinedType(
    Format.Native,  
    IsByteOrdered = true, 
    IsFixedLength = true,
    Name = "ExhaustiveInt",
    ValidationMethodName = "Validate"
)]  
public struct ExhaustiveInt : INullable {  
    private Int32   the_value;  
    private bool    is_unknown;
  
    public bool IsNull { 
        get {
            return false; 
        }
    }  

    public static ExhaustiveInt Null {  
        get {  
            ExhaustiveInt an_exhaustive_int = new ExhaustiveInt();  
            an_exhaustive_int.is_unknown = true;
            an_exhaustive_int.the_value = 0;
            return an_exhaustive_int;  
        }  
    }  

    public ExhaustiveInt(SqlInt32 int_value) {
        this.the_value = int_value.Value;
        this.is_unknown = int_value.IsNull;

    }
    public static explicit operator ExhaustiveInt(SqlInt32 int_value) {
        return new ExhaustiveInt(int_value);
    }
    
    public static ExhaustiveInt Parse(SqlString string_value) {
        ExhaustiveInt v = new ExhaustiveInt();  
        if(string_value.IsNull) {
            v.is_unknown = true;
            v.the_value = 0;
        }
        else {
            v.is_unknown = false;
            v.the_value = Int32.Parse(string_value.Value);
        }
        return v;
    }

    public override string ToString()  {  
        if (this.is_unknown)  
            return "Unknown";  
        else 
            return this.the_value.ToString(); 
    }  

    private bool Validate()  {  
        return true;  
    }  
  
    [SqlMethod(OnNullCall = false)]  
    public bool IsUnknown()  {  
        return this.is_unknown;  
    }  
}  

It seems, however, that SQL Server is neglecting both the specific constructor and the operator. Is there a way to make SQL Server understand that there is a way to initialize the struct directly from an int ?

I re-checked the documentation again and this time noticed two things:

  1. the Parse method requires an input of SqlString (as the purpose is to convert from a string), and
  2. overloaded constructors are allowed if the UDT is defined in a class, not a struct:

    A UDT defined in a class must have a public constructor that takes no arguments. You can optionally create additional overloaded class constructors.

I have tried a few things (using the code you provided in the question) but so far haven't been able to get it working as you desire. I'm not sure that even with overloaded class constructors that this will work as you are wanting it to. I believe the ability to do overloaded constructors is purely for your internal use within CLR code, not being accessed from the external T-SQL context. I believe that UDTs are instantiated internally (perhaps explaining the requirement for the constructor taking no arguments) and hence already exist by the time we get to interact with them, even in a DECLARE statement. Meaning, we can call it as follows:

  1.  DECLARE @Int dbo.ExhaustiveInt = NULL; DECLARE @Int dbo.ExhaustiveInt = 5;
    Both of those call the Parse method.
  2.  DECLARE @Int dbo.ExhaustiveInt = 0x1234567801; -- This is usually in the form of passing in another variable -- of the same UDT, or setting via: @Var = @Var.Method(val);
    That deserializes the value via the Read method (handled internally when using Format.Native , or required when using Format.UserDefined ).

In both cases, the type already exists in order to call either Parse or Read .


Also, I don't think it's going to use this:

 public ExhaustiveInt(SqlInt32 int_value) {
        this.the_value = int_value.Value;
        this.is_unknown = int_value.IsNull;

    }

If it did, and a NULL was passed in, it would error as you are not checking IsNull , but Value would be a null reference if int_value.IsNull was true.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM