2009-10-20 10 views
11

Tworzę "rodzajowy" wrapper powyżej procedur SQL i mogę rozwiązać wszystkie wymagane nazwy parametrów i sqltypes, ale czy istnieje sposób, aby uzyskać "podstawowy" typ .NET?Jak pobrać typ .NET danego parametru StoredProcedure w SQL?

Moim celem jest, aby zrobić coś takiego:

SqlParameter param; 
object value; 
object correctParam = param.GetNETType().GetMethod("Parse", 
    new Type[] { typeof(string) }).Invoke(value.ToString()); 
param.Value = correctParam; 

Gdzie GetNETType jest rzeczą muszę. Wiem, że można go zapisać jako przełącznik wewnątrz param.SqlDbType, ale jest to krótsza droga, a krótszy skomentowany kod oznacza niższą konserwację :)

+0

Co dokładnie próbujesz zrobić? Czy mam rację, gdy czytam, że masz kolekcję SqlParameters i masz kolekcję wartości .net? A chcesz "przekonwertować" swoją kolekcję wartości na właściwości SqlParameter.Value? Przykład: SqlParamaters [0].Wartość = MagicConvert (Wartości [0]); – Zenuka

Odpowiedz

12

Niestety, o ile wiem, to mapowanie nie jest ujawnione w kodzie wewnątrz .NET Framework. Przeglądałem wcześniej źródło referencyjne .NET Framework i odkryłem, że w kodzie .NET jest dużo długich instrukcji przełączania na różne typy, tak jak te, których próbujesz uniknąć, ale żaden z nich nie wygląda być odsłoniętym na zewnątrz.

Jeśli naprawdę chcesz tylko mapować z SqlTypes do najbardziej podobnego typu .NET, myślę, że najlepiej jest po prostu zamienić tabelę mapowania in the MSDN docs na kod. Zauważ, że tabela w MSDN ma (przynajmniej) dwa błędy: # 1: nie ma typu .NET o nazwie "DateTime2" (użyłem DateTime) i nie ma również typu o nazwie "Xml" (użyłem SqlXml).

W każdym razie, oto mapowanie, którego używałem - używanie Słownika zamiast przełącznika dla łatwego dostępu bez oddzielnej metody.

public static Dictionary<SqlDbType, Type> TypeMap = new Dictionary<SqlDbType, Type> 
{ 
    { SqlDbType.BigInt, typeof(Int64) }, 
    { SqlDbType.Binary, typeof(Byte[]) }, 
    { SqlDbType.Bit, typeof(Boolean) }, 
    { SqlDbType.Char, typeof(String) }, 
    { SqlDbType.Date, typeof(DateTime) }, 
    { SqlDbType.DateTime, typeof(DateTime) }, 
    { SqlDbType.DateTime2, typeof(DateTime) }, 
    { SqlDbType.DateTimeOffset, typeof(DateTimeOffset) }, 
    { SqlDbType.Decimal, typeof(Decimal) }, 
    { SqlDbType.Float, typeof(Double) }, 
    { SqlDbType.Int, typeof(Int32) }, 
    { SqlDbType.Money, typeof(Decimal) }, 
    { SqlDbType.NChar, typeof(String) }, 
    { SqlDbType.NText, typeof(String) }, 
    { SqlDbType.NVarChar, typeof(String) }, 
    { SqlDbType.Real, typeof(Single) }, 
    { SqlDbType.SmallInt, typeof(Int16) }, 
    { SqlDbType.SmallMoney, typeof(Decimal) }, 
    { SqlDbType.Structured, typeof(Object) }, // might not be best mapping... 
    { SqlDbType.Text, typeof(String) }, 
    { SqlDbType.Time, typeof(TimeSpan) }, 
    { SqlDbType.Timestamp, typeof(Byte[]) }, 
    { SqlDbType.TinyInt, typeof(Byte) }, 
    { SqlDbType.Udt, typeof(Object) }, // might not be best mapping... 
    { SqlDbType.UniqueIdentifier, typeof(Guid) }, 
    { SqlDbType.VarBinary, typeof(Byte[]) }, 
    { SqlDbType.VarChar, typeof(String) }, 
    { SqlDbType.Variant, typeof(Object) }, 
    { SqlDbType.Xml, typeof(SqlXml) }, 
}; 

Należy pamiętać, że jedna rzecz, trzeba zwrócić uwagę na to rozmiar/precision-- niektóre typy SQL (np varchar) mają limity rozmiaru, natomiast typy .NET (np string) nie. Zatem znajomość najbardziej prawdopodobnego typu .NET nie jest wystarczająca ... jeśli używasz tego na przykład do sprawdzania reguł sprawdzania poprawności, musisz także mieć możliwość uniemożliwienia użytkownikom wprowadzania nieprawidłowych wartości (np. Zbyt dużych), wiedząc więcej o parametrze, np. precyzji. Zwróć uwagę, że jeśli zajrzysz do źródła SqlClient, używają specjalnego kodu do obsługi przypadków, takich jak ustawienie precyzji typu dziesiętnego z odpowiedniej precyzji SQL.

Należy pamiętać, że jeśli jedynym powodem, dla którego potrzebujesz typu .NET, jest możliwość dodania danych do zapisanego parametru proc, możesz spróbować użyć ToString() dla wszystkich wartości .NET, wstawiając ciąg znaków do właściwość Value parametru SqlParameter i zobacz, czy framework wykona dla Ciebie konwersję/parsowanie. Na przykład dla parametru XML lub Date możesz odejść z wysyłaniem łańcucha znaków.

Ponadto, zamiast używać refleksji do znalezienia metody Parse() dla każdego typu, ponieważ istnieje znana (i mała) lista typów, można uzyskać lepszą wydajność za pomocą silnie wpisanego kodu parsowania dla każdego, np. kod poniżej. (Zwróć uwagę, że kilka typów (na przykład SqlDbType.UDT) nie muszą mieć oczywisty parser method-- będziesz musiał dowiedzieć się, w jaki sposób chcesz obsłużyć tych.)

public static Dictionary<SqlDbType, Func<string, object>> TypeMapper = new Dictionary<SqlDbType, Func<string, object>> 
{ 
    { SqlDbType.BigInt, s => Int64.Parse(s)}, 
    { SqlDbType.Binary, s => null }, // TODO: what parser? 
    { SqlDbType.Bit, s => Boolean.Parse(s) }, 
    { SqlDbType.Char, s => s }, 
    { SqlDbType.Date, s => DateTime.Parse(s) }, 
    { SqlDbType.DateTime, s => DateTime.Parse(s) }, 
    { SqlDbType.DateTime2, s => DateTime.Parse(s) }, 
    { SqlDbType.DateTimeOffset, s => DateTimeOffset.Parse(s) }, 
    { SqlDbType.Decimal, s => Decimal.Parse(s) }, 
    { SqlDbType.Float, s => Double.Parse(s) }, 
    { SqlDbType.Int, s => Int32.Parse(s) }, 
    { SqlDbType.Money, s => Decimal.Parse(s) }, 
    { SqlDbType.NChar, s => s }, 
    { SqlDbType.NText, s => s }, 
    { SqlDbType.NVarChar, s => s }, 
    { SqlDbType.Real, s => Single.Parse(s) }, 
    { SqlDbType.SmallInt, s => Int16.Parse(s) }, 
    { SqlDbType.SmallMoney, s => Decimal.Parse(s) }, 
    { SqlDbType.Structured, s => null }, // TODO: what parser? 
    { SqlDbType.Text, s => s }, 
    { SqlDbType.Time, s => TimeSpan.Parse(s) }, 
    { SqlDbType.Timestamp, s => null }, // TODO: what parser? 
    { SqlDbType.TinyInt, s => Byte.Parse(s) }, 
    { SqlDbType.Udt, s => null }, // consider exception instead 
    { SqlDbType.UniqueIdentifier, s => new Guid(s) }, 
    { SqlDbType.VarBinary, s => null }, // TODO: what parser? 
    { SqlDbType.VarChar, s => s }, 
    { SqlDbType.Variant, s => null }, // TODO: what parser? 
    { SqlDbType.Xml, s => s }, 
}; 

kod używany powyżej jest całkiem proste, na przykład :

 string valueToSet = "1234"; 
     SqlParameter p = new SqlParameter(); 
     p.SqlDbType = System.Data.SqlDbType.Int; 
     p.Value = TypeMapper[p.SqlDbType](valueToSet); 
3

Myślę, że brakuje tu kroku. Pierwszą rzeczą, którą musisz zrobić, jest zapytanie do bazy danych o definicję przechowywanego procesu poprzez wywołanie select i wewnętrzne dołączenie do tabeli obiektów sys lub za pomocą opakowania zarządzania. Następnie możesz "wywnioskować" typy parametrów na podstawie zwróconych informacji.

Oto MSO lin k, aby zacząć

a przykładem how to query the database structure bezpośrednio

Po uruchomieniu SQL z drugim przykładzie przeciwko bazy danych zobaczysz dokładnie to, co się dzieje:

USE AdventureWorks; 
GO 
SELECT SCHEMA_NAME(SCHEMA_ID) AS [Schema], 
SO.name AS [ObjectName], 
SO.Type_Desc AS [ObjectType (UDF/SP)], 
P.parameter_id AS [ParameterID], 
P.name AS [ParameterName], 
TYPE_NAME(P.user_type_id) AS [ParameterDataType], 
P.max_length AS [ParameterMaxBytes], 
P.is_output AS [IsOutPutParameter] 
FROM sys.objects AS SO 
INNER JOIN sys.parameters AS P 
ON SO.OBJECT_ID = P.OBJECT_ID 
WHERE SO.OBJECT_ID IN (SELECT OBJECT_ID 
FROM sys.objects 
WHERE TYPE IN ('P','FN')) 
ORDER BY [Schema], SO.name, P.parameter_id 
GO 
+0

Nie jestem pewien, jak to się robi (im rozszerzenie cudzego kodu), ale interakcja za pośrednictwem SqlCommand.Parameters pokazuje mi wszystkie wymagane parametry. Jak mogę wywnioskować typy? – nothrow

+0

Naprawdę nie można ustalić typ. Musisz jakoś poznać typ. Zwykle typ jest zakodowany na stałe, ale szukasz zautomatyzowanego sposobu, a jedynym sposobem, aby to zrobić, aby "poznać" typ, jest zapytanie do bazy danych. –

+0

Myślę, że może potrzebować sposobu mapowania z typu SQL na odpowiedni typ .NET. na przykład Varchar => String, itp. – Knobloch

3

Można niekoniecznie niejawnie i dokładnie wyodrębnić prawidłowy .NET CTS („podstawowe”) typu, ponieważ może ona zmieniać w zależności od wartości parametru - w SqlParameter za .DbType i .SqlDbType są zmienne i jawnie ustawialne przez programistę (lub silnik generujący kody) W przypadku parametru wyjściowego typ .DbType/.SqlDbType może być niepoprawny nawet po tym, jak był odpowiedni przez pewien czas, na przykład, jeśli wartość poniżej, która powraca nagle, jest inny niż oczekiwano w terminach .NET. Wartości są sterowane przez magazyn danych, a .NET SqlParameter radzi sobie najlepiej, jak potrafi, ze swoimi jawnymi typami. Wartość danych SqlParameter należy uznać za słabo wpisane w terminach .NET (potwierdzone przez wartość zwracaną System.Object właściwości parm.Value).

Najprościej jest

  1. Użyj jednej z metod mapowania nakreślonych przez innych plakatów - oczywiście, że ma swój własny implicite założenie, że typ SQL Parametr zawsze będzie prawidłowy dla danych w nim.
  2. prawdopodobnie przetestuj wartość wracającą z parametru wyjściowego i przyjmij, że kolejne wartości są tego samego rodzaju. Oczywiście to zależy od bazy danych.
  3. Znajdź inną strategię zamiast polegać na przestrzeni nazw Microsoft Sql - możesz być o wiele szczęśliwsza w przyszłości.

Testowanie wartości dla typu .NET CTS będzie wyglądało jak System.Type t = paramInstance.Value.GetType(); Null spowoduje wyjątek. Nadal trzeba go rzucać odpowiednio za pomocą przełącznika lub jeśli/inaczej, chyba że wyciągniesz jakieś fantazyjne techniki odbicia.

1

Jeśli możesz rozwiązać problem z właściwym typem SqlType, Reflection dostarczy Ci wyraźną rzutowanie na typ .NET. Wartością zwracaną będzie bazowy typ System.Type. Zbuforowanie wyniku powinno nadrobić perfek w pierwszym wyszukiwaniu.

1

Zobacz, co robią w linq to sql t4, wygląda na to, że działa dobrze.

Być może będziesz w stanie dowiedzieć się, czego potrzebujesz, patrząc na kod.

4

Nikt inny nie chce ci powiedzieć, ale to, co robisz, prawdopodobnie nie jest najlepszym sposobem na zrobienie tego.

object correctParam = param.GetNETType().GetMethod("Parse", 
    new Type[] { typeof(string) }).Invoke(value.ToString()); 
param.Value = correctParam; 

Mówisz, że jesteś dany ciąg znaków, który znasz musi być przypisana do parametru, a chcesz wepchnąć tę wartość w jakiś sposób, że można go zmieścić?

Zastanów się, dlaczego to robisz. Jesteś przy założeniu, że następujący kod ma rację:

param.Value = NetType.Parse(value.toString()) 

nie ma jasny powód, dlaczego tak jest lepiej niż:

param.Value = value; 

Ale skoro chcesz to zrobić, wydaje się bezpiecznie założyć, że próbujesz tego i okazało się, że twoim prawdziwym problemem jest to, że value nie jest właściwym typem dla tego parametru.Tak więc potrzebujesz magicznej poprawki, którą możesz uruchomić, aby zawsze upewnić się, że właściwy jest value. Co naprawdę chcesz to prawdopodobnie:

SetParam(param, value);

Jeżeli ta funkcja spożywcze wartość do parametru. To naprawdę ułatwia sprawę, jeśli value nie jest po prostu typu object, jak mówisz, ale ma prawdziwy typ (jak int lub string). Dzieje się tak dlatego, że możesz użyć metody przeciążania, takiej jak SetParam(SqlParam param, int value) lub generics, aby wywnioskować typ wartości SetParam<T>(SqlParam param, T value).

Znamy więc funkcję, której chcemy, a czego nie wiemy, dlaczego. W większości rozsądnych scenariuszy masz pojęcie o typach wartości, a także masz pojęcie o typie parametru. Pytasz o sposób, aby zapchać wartość, która nie pasuje do parametru, do parametru, którego nie rozumiesz.

Istnieją dwa główne powody, mogę myśleć o tym wniosku:

  1. Ty w rzeczywistości wiemy, że typy są kompatybilne i szukasz ogólny sposób to zrobić, aby uniknąć pisania dużo kod. A więc wiesz, że próbujesz przypisać parametr long do parametru o nazwie SqlInt i polegasz na konwersjach ciągów, aby wyeliminować problemy z bezpieczeństwem typu.

  2. Nie bardzo rozumiesz kod, którego używasz i próbujesz załatać poprawkę, aby coś działało.

To naprawdę ważne, aby być uczciwym wobec siebie o takim przypadku jesteś w. Jeśli jesteś w pierwszym przypadku, to można napisać metodę jak SetParam że opisany powyżej dość łatwo. Będziesz musiał napisać instrukcję switch (lub jak najlepszą odpowiedź powyżej, wyszukiwanie Dictionary). Będziesz musiał stracić precyzję (rzucanie długiego na int nie działa dla dużych liczb, ale też nie będzie to twoja analiza), ale zadziała.

Jeśli jesteś w drugim przypadku, zatrzymaj się na minutę. Rozpoznaj, że ustawiasz się na więcej błędów w przyszłości (ponieważ konwersja zi na ciąg nie rozwiąże problemów, które masz, nie rozumiejąc typów). Wiesz, że potrzebujesz pomocy, dlatego właśnie korzystasz z Stack Overflow i oferujesz nagrodę za pomoc, a ty masz do czynienia z bazą kodu, której nie rozumiesz. Mogę powiedzieć teraz na podstawie twojego pytania, że ​​masz zamiar wykopać sobie głębszą dziurę, niż zdajesz sobie sprawę, że to jest twoja sytuacja, ponieważ już odmówiłeś najlepszej odpowiedzi (zrobić instrukcję przełączania na podstawie typu parametru) bez wyraźnego powodu .

Tak więc, jeśli jesteś w drugim przypadku, rzecz, która pomoże ci najbardziej, nie jest odpowiedzią ze Stack Overflow, chyba że chcesz opisać swój prawdziwy problem bardziej kompletnie. Co pomoże ci zrozumieć, skąd pochodzą wartości (czy to interfejs użytkownika? Czy jest to inny podsystem, do jakich reguł się odnoszą?) Czy istnieje powód, dla którego typy nie pasują?) I gdzie idą (co to jest definicja wywoływanej procedury składowanej, jakie typy parametrów są zdefiniowane jako?). Wyobrażam sobie, że prawdopodobnie nie musisz nawet wchodzić w SQL, aby to znaleźć, ponieważ ktokolwiek ci podarował SqlParam, prawdopodobnie już to zdefiniował dla ciebie. Jeśli to zdefiniowałeś, powinieneś natychmiast przejść do SQL, aby to zrozumieć.

Powiązane problemy