14/04/2011

CURRENT_USER , SYSTEM_USER...

Home

W poście Problem z domyślnym schematem wspomniałem, że w celu wyjaśnienia problemu posłużyłem się funkcją CURRENT_USER, która zwraca nazwę bieżącego użytkownika. Czym jednak funkcja ta różni się od funkcji SYSTEM_USER, SESSION_USER czy innych o podobnych nazwach. Sprawa jest prosta. Część z tych funkcji zwraca login, który służy do uwierzytelnienie się względem serwera, a część nazwę użytkownika, która określa co możemy zrobić w ramach poszczególnych baz danych (autoryzacja). Każdy login jest skojarzony (zmapowany) z jednym użytkownikiem dla danej instancji bazy danych. Nie jest to nic skomplikowanego ale można się zgubić w gąszczu nazw. Dlatego ku pamięci, głównie dla siebie, zebrałem te informacje w postaci poniższej tabelki.

CURRENT_USERZwracają nazwę użytkownika
USER_NAME
SESSION_USER
USER

SYSTEM_USERZwracają login
SUSER_SNAME

Więcej szczegółów np.: parametry wywołania, wyjaśnienie skąd wzięło się tyle funkcji robiących to samo, można znaleźć tutaj.

13/04/2011

Problem z domyślnym schematem

Home

Kilka dni temu chciałem zmodyfikowałem domyślny schemat dla jednego z użytkowników, niech nazywa się SomeUser. W tym celu otworzyłem Management Studio, wybrałem interesującą mnie bazę danych i przeszedłem do listy użytkowników. Dalej wybrałem interesującego mnie użytkownika i wyświetliłem dla niego okno właściwość i w polu Default schema: wpisałem nazwę schematu, dla ustalenia uwagi niech nazywa się testSchema.

Żeby być pewnym, że wszystko jest w porządku nawiązałem połączenie z serwerem korzystając z wspomnianego użytkownika i spróbowałem wykonać trywialne zapytanie pobierające dane z tabeli znajdującej się w schemacie testSchema:
select *
from TestTable
Ku swojemu zdziwieniu otrzymałem komunikat o treści Invalid object name 'TestTable'. Sprawdziłem, więc zapytanie zawierające pełną nazwę tabeli (razem ze schematem) i zadziałało:
select *
from testSchema.TestTable
Aby upewnić się, że pracuję w kontekście właściwego użytkownika posłużyłem się poleceniem select current_user, które ponownie ku mojemu zdziwieniu wypisało na ekran dbo zamiast SomeUser. No cóż może się pomyliłem. Ponownie połączyłem się z serwerem upewniając się, że korzystam z dobrego użytkownika ale nic się nie zmieniło.

Problem pomógł mi rozwiązać kolega, który zwrócił uwagę, że SomeUser ma przypisaną rolę sysadmin, a więc w rzeczywistości był widziany jako użytkownik dbo (dla którego domyślny schemat to dbo). Po zabraniu użytkownikowi roli sysadmin, która nie była mu zresztą potrzebna, wszystko zaczęło działać jak trzeba.

01/04/2011

Zabawy z pozycjonowaniem okna w WPF

Home

Czasami zachodzi potrzeba "ręcznego" pozycjonowania okna w aplikacji WPF. Sprawa jest prosta i sprowadza się do odpowiedniego ustawienia właściwości Left oraz Top. Jeśli chcemy aby pozycja okna dziecka zależała od położenia rodzica to możemy odwołać się do rodzica przez właściwość Owner i napisać taki kod jak poniżej. Kod ten powoduje przesunięcie okno dziecka o 50 jednostek w prawo względem lewej krawędzi okna rodzica.
...
myWindow.Left = myWindow.Owner.Left + 50;
...
Nic prostrzego. Jest tu jednak mały haczyk. Jeśli okno rodzica jest zmaksymalizowane to właściwość Left i Top dla rodzica będą miały wartość wskazującą położenie okna przed zmaksymalizowaniem. Poniżej kod, który to uwzględnia:
...
myWindow.Left = (myWindow.Owner.WindowState == WindowState.Maximized ? 0 : myWindow.Owner.Left) + 50;
...
Nic trudnego ale trzeba o tym pamiętać. Sprawa robi się jeszcze ciekawsza kiedy pracujemy z wieloma monitorami. W takim wypadku powyższy kod będzie działał tylko dla głównego monitora. Jeśli okno rodzica będzie zmaksymalizowane i będzie znajdować się na innym monitorze niż główny to wykonanie tego kodu spowoduje wyświetlenie okna dziecka na głównym monitorze! Poniżej rozwiązanie tego problemu:

if (myWindow.Owner.WindowState == WindowState.Maximized)
{
  Screen s = Screen.FromHandle(new WindowInteropHelper(myWindow.Owner).Handle);                      
  myWindow.Left = s.WorkingArea.Left + 50;
}
else
{
  myWindow.Left = myWindow.Owner.Left + 50;
}
                    
Korzystam tutaj ze starej poczciwej klasy Screen, przy pomocy której znajduję monitor, na którym wyświetlane jest okno rodzica. Następnie odczytuję położenie lewej krawędzi tego monitora. Wartość ta będzie różna w zależności od tego, z którym monitorem mamy do czynienia.

23/03/2011

Przygody ze Steam'em

Home

Shogun: Total War to jedna z pierwszych gier jaką odpaliłem na swoim pierwszym komputerze PC i zarazem jedna z najlepszych gier w jakie grałem. Bardzo podobała mi się również inna gra z tej serii czyli Rome: Total War. Ostatnio postanowiłem nadrobić zaległości i kupiłem ostatnią grę z tej serii czyli Napoleon: Total War.

Napoleon: Total War jak wiele obecnie sprzedawanych gier wymaga do działania konta w serwisie Steam. Żaden problem ponieważ takie konto posiadam już od dawna. Niestety Steam potrafi być bardzo irytujący. Zacznę od tego, że po uruchamieniu klient Steam sprawdza czy są dostępne aktualizacje i pobiera je nie pytając użytkownika o zdanie. W tej chwili dysponuję połączeniem modemowym, a więc jest to dla mnie tym bardziej uciążliwe.

Na tym nie koniec. Zanim udało mi się uruchomić właściwy proces instalowania gry napotkałem jeszcze jeden problem. Po uruchomieniu instalatora pojawił sie błąd Failed to run install script. Szczęśliwie udało mi się go szybko rozwiązać, uruchamiając proces instalacji z wiersza poleceń przy pomocy komendy steam.exe -install h: gdzie h: to napęd DVD.

Kiedy instalacja zakończyła się i myślałem, że w końcu zagram okazało się, że Steam ma jeszcze coś do powiedzenia i rozpoczął aktualizację dopiero co zainstalowanej gry. Wyłączenie automatycznej aktualizacji we właściwościach Napoleon: Total War, odłączenie Internetu, ponowne uruchomienie klienta Steam nie pomogło. W tej chwili proces instalacji zakończył się i udało mi się uruchomić grę :) Tak sobie jednak myślę, że stare dobre czasy kiedy kupowało się grę, przychodziło do domu, instalowało i grało bez potrzeby aktywowania przez Internet, przymusowej aktualizacji itp. mijają albo już minęły.

19/03/2011

Jak właściwość może napsuć krwi

Home

Właściwości to jedna z moich ulubionych cech języka C#. W poście tym nie będę jednak przekonywał opornych, jeśli tacy są, do ich używania. Opiszę natomiast przypadek złego wykorzystania właściwości, który ostatnio napsuł mi trochę krwi. Załóżmy, że mamy aplikację WWW lub grubego klienta. Aplikacja ta komunikuje się z bazą danych za pośrednictwem tzw. dostawców danych czyli klas implementujących dobrze określone interfejsy. W zależności od konfiguracji dostawca może być dostępny lokalnie w ramach procesu aplikacji lub zdalnie czyli być uruchomiony na innej maszynie np. jako usługa WCF. Z perspektywy aplikacji jest to przezroczyste.

Dla ustalenia uwagi przyjmijmy, że dostawca udostępnia metodę Customer GetCustomer(int customerId), która zwraca obiekt klasy Customer reprezentujący klienta o podanym identyfikatorze. Problem polega na tym, że w zależności od tego czy dostawca danych działa lokalnie czy zdalnie to dla tego samego identyfikatora zwraca trochę inne dane. Dokładniej mówiąc jeśli mamy do czynienia z dostawcą lokalnym wszystko jest w porządku. Natomiast dla dostawcy zdalnego zwracane dane są niekompletne w tym sensie, że nie wszystkie właściwości obiektu Customer są ustawione. Co ciekawe po zdebugowaniu procesu serwera okazuje się, że zwracane przed dostawcę dane są poprawne ale po dotarciu do aplikacji okazują się jednak niekompletne.

Początkowo przecierałem oczy ze zdziwienia bo jak to możliwe, że część danych nagle znika. Po zagłębieniu się w problem znalazłem jednak winnego, którym okazała się błędne zaimplementowana właściwość w połączeniu z serializacją XML'ową. Istotę problemu przedstawia poniższy kod:

private string _StringValue;
public string StringValue
{
  get
  {
    if(_StringValue == "aaa")
      return "Ala ma kota";
    else if(_StringValue == "bbb")
      return "Kot ma Alę";
    ...
    else
      return null;
  }
  set
  {
    _StringValue = value;
  } 
}

Jak widać wartość zwracana przez getter właściwości jest inna niż wartość ustawiana przy pomocy settera. W przypadku kiedy dostawca działa lokalnie nie stanowi to żadnego problemu ponieważ pracujemy bezpośrednio z klasą dostawcy danych. W przypadku kiedy dostawca uruchomiony jest zdalnie to w rzeczywistości po stronie aplikacji pracujemy z obiektem proxy. W momencie wywołania metody GetCustomer do serwera wysyłane jest odpowiedni komunikat i wywoływana jest "prawdziwa" metoda GetCustomer. W wyniku otrzymujemy obiekt klasy Customer, która musi zostać przesłany do proxy. W tym celu obiekt musi zostać zserializowany czyli zapisany do strumienia i wysłany przez sieć.

I tutaj jest pies pogrzebany. Jeśli zostanie użyta serializacja XML'owa czyli taka, która "zamieni" obiekt na dokument XML to do proxy zostanie wysłana wartość zwrócona przez getter właściwości czyli coś innego niż wartość, która została ustawiona przy pomocy settera np.: jeśli do ustawienia właściwości StringValue użyjemy łańcucha "aaa" to w dokumencie XML zostanie zapisana wartość "Ala ma kota".

Po odebraniu odpowiedzi proxy spróbuje odtworzyć obiekt na podstawie odebranego dokumentu XML. Do zainicjowania właściwości StringValue użyje wartości z dokumentu czyli łańcucha "Ala ma kota". Teraz jeśli odczytamy właściwości StringValue to dostaniemy null ponieważ kod w getterze nie rozpozna łańcucha "Ala ma kota". Jeśli użyjemy serializacji binarnej problem nie wystąpi ponieważ w strumieniu zostanie zapisana wartość prywatnego pola _StringValue. Jeśli chcemy pozostać przy serializacji XML'owej to należy trochę zmodyfikować kod. Właściwość StringValue powinna być tylko do odczytu. Należy również stworzyć dodatkową właściwość do ustawiania danych.

public string StringValueForSerialization
{
  get; set;
}

public string StringValue
{
  get
  {
    if(StringValueForSerialization == "aaa")
      return "Ala ma kota";
    else if(StringValueForSerialization == "bbb")
      return "Kot ma Alę";
    ...
    else
      return null;
  }
}