Author Archives: Robert Haken

avatar Neznámé

About Robert Haken

Software Architect, Founder at HAVIT, Microsoft MVP - ASP.NET/IIS

Rozdíl mezi COUNT(col) a COUNT(*)

COUNT(*)

dává počet všech řádek

COUNT(col)

dává počet řádek, které mají hodnotu sloupce col jinou než NULL

sp_who2 – nedokumentovaná procedura MSSQL

Kromě klasické sp_who má MSSQL server (tuším 7.0+) ještě nedokumentovanou procedurusp_who2, která kromě základní informací z sp_who (ne všech!) přidává ještě několik dalších podrobností o aktuální uživatelské aktivitě.

Jako nedokumentovaná procedura je celkem nepoužitelná do produkčního prostředí, ale při vývoji a ladění se může hodit. Například je tam šikovný údaj BlkBy (Blocked By), který udává, na jaký jiný proces se čeká, aby se mohlo pokračovat.

IComparable

Rozhraní IComparable, popř. strong-typed IComparable<T> slouží jako podpora pro porovnávání prvků, zejména pro jejich řazení (metody Sort). K implementaci je jediná instanční metoda CompareTo(), jejíž výsledek má být:

  • nula, pokud jsou instance stejné (z hlediska porovnání rovnocenné),
  • záporný, pokud je je instance (this) menší než parametr metody (při vzestupném řazení bude this dříve než parametr),
  • kladný, pokud je instance (this) větší než parametr metody (při vzestupném řazení bude parametr dříve než this).

Tato konvence vyplynula z metodiky používané pro porovnávání čísel prostým odečtením this-param.

Dále musíme dodržet následující definiční pravidla, na která se často zapomíná:

  • null < cokoliv jiného,
  • null = null (neimplementuje se)

Samozřejmě taky všechna klasická pravidla:

  • když A = B a B = C, tak A = C,
  • když A < B a B < C, tak A < C,
  • když A < B, tak B > A,

Vzorová implementace obou rozhraní by pak mohla vypadat takto:

 public class MyComparableClass : IComparable, IComparable<MyComparableClass>
{
   // Strong-typed implementace IComparable<T>
   public int CompareTo(MyComparableClass other)
   {
      if (other == null)
      {
         return 1;
      }
      // naše implementace srovnání, například
      return this.Value.CompareTo(other.Value);
   }
   // Implementace IComparable
   public int CompareTo(object obj)
   {
      if (obj == null)
      {
         return 1;
      }
      if (obj is MyComparableClass)
      {
         return CompareTo(obj as MyComparableClass);
      }
      throw new ArgumentException("obj musí být typu MyComparableClass", "obj");
   }
}

Doporučuje se, aby třída, která implementuje IComparable, overridovala i metodu Equals.

RAISERROR a jeho efekt v ADO.NET

RAISERROR

Z SQL stored procedur můžeme oznamovat vnější aplikaci chyby prostřednictvím příkazu RAISERROR, nejběžněji:

RAISERROR(message_str, severity_int, state_int, arg1, arg2)

přičemž

  • message_str představuje textový popis chyby v like-PRINTF podobě s procentama (argn pak představují hodnoty k dosazení),
  • severity_int udává závažnost chyby (viz níže),
  • state_int je číslo od 1 do 127 a předává se tím stavová hodnota (obvykle prostě 1)

např. tedy

RAISERROR ('The level for job_id:%d should be between %d and %d.',
      16, 1, @@JOB_ID, @@MIN_LVL, @@MAX_LVL)
ADO.NET

A teď k efektu RAISERROR v ADO.NET – vše závisý na parametry severity. Pro uživatelské chyby jsou určeny severity od 1 do 18, severity od 19 do 25 jsou určeny pro sysadmina.

Výsledký efekt v ADO.NET je tedy následující:

  • severity <= 10 nevyvolá výjimku v .NET, nýbrž event InfoMessage související SqlConnection (k odchycení musíme navěsit event-handler),
  • severity > 10 vyvolá výjimku SqlException, ale nezavře spojení,
  • severity > 16 vyvolá výjimky SqlException a uzavře spojení.

POZOR!!! Samotné RAISERROR nezajistí ROLLBACK transakce (musí se provést explicitně), ani nepřeruší běh T-SQL dávky (nutno třeba RETURN).

ICloneable

ICloneable je rozhraní (interface) .NET Frameworku pro podporu klonování objektů, tedy pro vytváření nových instancí třídy se stejnými hodnotami, jako má instance existující.

Základním kamenem pro implementaci ICloneable je protected metoda Object.MemberwiseClone(), která vytvoří shallow-copy (mělkou kopii) objektu. Mělkost kopie spočívá v tom, že jsou zkopírovány pouze přímé hodnoty objektu, což u referenčních typů má za následek pouze vytvoření nových referencí na stejnou instanci.

Korektní implementace rozhraní ICloneable by měla vypadat asi takto:

class MyCloneableClass : System.ICloneable
{
   // Explicitní implementace metody rozhraní.
   // Potřebná pro klienty ICloneable, ale neviditelná 
   // běžné klienty MyCloneableClass
   object ICloneable.Clone()
   {
      // prostě vrátíme hodnotu naší implementace
      return this.Clone(); 
   }
   // Silně-typovaná metoda Clone
   public virtual MyCloneableClass Clone()
   {
      // běžná memberwise-kopie vlastních hodnot
      MyCloneableClass x = this.MemberwiseClone() as MyCloneableClass;
      // následuje deep-copy všeho, co je potřeba
      x.somethingDeep = this.somethingDeep.Clone();
      return x;
   }
}

Pokud ICloneable korektně implementujeme u všech našich tříd, bude vše velmi primitivní. Vlastní klonování bude probíhat v kaskádě, resp. procházením stromu do hloubky. Problém samozřejmě nastane, když budeme mít člen referenčního typu, který neimplementuje ICloneable.

Vyhledávání nezávislé na diakritice pomocí COLLATE

Pomocí explicitního nastavení collation lze jednoduše udělat vyhledávání nezávislé na diakritice a velkých/malých písmenech:

 SELECT 'Ano!' WHERE ('ABCacžžzř' COLLATE Latin1_General_CI_AI LIKE 'abcáčžzžr' COLLATE Latin1_General_CI_AI) 

Celý tento způsob má několik problematických otazníků, které jsem zatím neprobádal:

  • není mi úplně jasné, jaký je vztah mezi collation a indexy, v mém konkrétním případě nedošlo ke změně execution planu a operace zůstala „Clustered Index Scan“.
  • není mi úplně jasné, proč některé collation fungují, a některé ne – např. „Czech_CI_AI“ mi očekávané výsledky nedávalo.

Update: Czech_CI_AI

Po letech jsem se dopátral, jak je to s Czech_CI_AI. Věc se má tak, že v češtině jsou C-Č, D-Ď, R-Ř, … různá písmena (na rozdíl od A-Á, E-Ě, …) a collation Czech_CI_AI to důsledně rozlišuje (na rozdíl od Latin1_General_CI_AI).

Vlastní primitivní Repeater – templated data-bound control

Primitivní Repeater

Začněme velmi jednoduchým příkladem, primitivní napodobeninou Repeateru, na níž si ukážeme základy vytváření složených datově-vázaných controlů (composite data-bound controls) a použití šablon v controlech (templates).

Dejme tomu chceme vytvořit control, který bude podle šablon vypisovat seznam zákazníků. Oproti standardnímu Repeateru z ASP.NET však bude pracovat se strong-typed prvky a přidáme si jednoduchou podporu pro načítání šablon ze souborů. Spokojíme se prozatím bez podpory ViewState (data bude tedy nutno bindovat v každém roundtripu) i bez eventů (IremCreated, ItemDataBound, Command, …).

<havit:MyRepeater ID="cosi" runat="server">
      <ItemTemplate>
         Jméno: <%# Container.Zakaznik.Jmeno %><br/>
         Datum narození: <%# Container.Zakaznik.DatumNarozeni.ToShortDateString() %></br>
      </ItemTemplate>
</havit:MyRepeater>

Základy práce se šablonami (templates)

Princip šablon je velmi jednoduchý. Když si uvědomíme, že controly tvoří hiearchii (kořenem je Page a podřízené controly jsou vždy v kolekci Controls), pak použití šablony není nic jiného než připojení nové větve controlů do této hiearchie.

V controlu se šablona samotná vytvoří pomocí property typu ITemplate.

[PersistenceMode(PersistenceMode.InnerPropety)]
[TemplateContainer(typeof(MyRepeaterItem))]
public ITemplate ItemTemplate
{
      get { return _itemTemplate; }
      set { _itemTemplate = value; }
}

Atribut PersistenceMode nastavený na InnerPropety nám říká, že ItemTemplate bude ve zdrojovém kódu controlu zapsán jako vnořený tag. Atribut TemplateContainerzase parseru říká, jakého typu je objekt Container, který budeme používat uvnitř template.

Pro začátek je to vše, co potřebujeme pro definici šablony, parser se sám postará o naplnění property ItemTemplate, pokud ji v .aspx stránce použijeme.

Rozhraní ITemplate definuje jedinou metodu InstantiateIn(Control container), která nedělá nic jednoduššího, než že připojí control-hiearchy tvořené šablonou jako větev do containeru (toho containeru, jehož typ jsme udali v atributu TemplateContainer).

Nutno zdůraznit, že container, do kterého template připojujeme, musí implementovat rozhraní INamingContainer, aby nám korektně fungoval objekt Container v kódu šablony.

Základy vytváření data-bound controls

Pro vytváření data-bound control je v ASP.NET 2.0 připraven pattern v podobě abstraktní třídy CompositeDataBoundControl. Composite proto, že nové controly vytváříme skládáním jiných controlů do hiearchie, nikoliv renderováním HTML kódu controlů úplně nových.

Základní implementace se v podstatě celá odehrává v metodě CreateChildControls(IEnumerable dataSource, bool dataBinding), přičemž základní pattern chování by měl být zhruba následující

override int CreateChildControls(IEnumerable dataSource, bool dataBiding)
{
      if (dataBinding)
      {
            // vytahej z dataSource data a ulož si je ve své podobě, obvykle do this.Items
            // this.Items bychom si měli ukládat do ViewState, drží nám data mezi postbacky
      }



      // vytvoř controly pro každou položku this.Items a přidej ji do this.Controls
      // foreach (MyItem item in Items) { ... Controls.Add(..); }
      // return počet vytvořených items
}

Pokud nemáme ViewState, tak lze samozřejmě obě fáze sloučit a rovnou tvořit control-hiearchy z dat v dataSource.

MyRepeater – kombinace CompositeDataBoundControl a ITemplate

No a není nic jednoduššího, než obě možnosti zkombinovat a vytvořit tak control, který bude data-bound a pro reprezentaci dat bude používat šablony:

public class MyRepeater : CompositeDataBoundControl
{
      [PersistenceMode(PersistenceMode.InnerPropety)]
      [TemplateContainer(typeof(MyRepeaterItem))]
      public ITemplate ItemTemplate
      {
            get { return _itemTemplate; }
            set { _itemTemplate = value; }
      }
      private ITemplate _itemTemplate;

      /// <summary>
      /// Soubor .ascx, který představuje případnou externí ItemTemplate.
      /// </summary>
      public string ItemTemplateFile
      {
            get { return _itemTemplateFile; }
            set { _itemTemplateFile = value; }
      }
      private string _itemTemplateFile;
      public Collection<MyRepeaterItem> Items
      {
            get
            {
                  if (_items == null)
                  {
                        _items = new Collection<MyRepeaterItem>();
                  }
                  return _items;
            }
      }
      private Collection<MyRepeaterItem> _items;
      protected override CreateChildControls(IEnumerable dataSource, bool dataBinding)
      {
            // vytaháme data z dataSource
            if (dataBinding)
            {
                  if (dataSource != null)
                  {
                        foreach (object o in dataSource)
                        {
                              if (!(o is Zakaznik))
                              {
                                    throw new ApplicationException("DataSource musí obsahovat prvky typu Zakaznik.");
                              }
                              MyRepeaterItem item = new MyRepeaterItem(o as Zakaznik);
                              Items.Add(item);
                        }
                  }
            }
            // vytvoříme controly
            foreach (MyRepeaterItem in Items)
            {
                  if (!String.IsNullOrEmpty(ItemTemplateFile))
                  {
                        _itemTemplate = Page.LoadTemplate(ItemTemplateFile);
                  }
                  _itemTemplate.InstantiateIn(item);
                  item.DataBind();
                  Controls.Add(item);
            }
            return Items.Count;
      }
}

Kód ukazuje i druhou možnost získávání šablon – jejich načítání z .ASCX souboru pomocí Page.LoadTemplate(). Šablona totiž může úplně stejně, jako ve vlastní .aspx stránce, být uložena v samostatném .ASCX souboru (jen pro Container je tam potřeba použít explicitní přetypování, protože tam parser neví o atributu TemplateContainer).

Pro úplnost ještě MyRepeaterItem:

public class MyRepeaterItem : Control, INamingContainer
{
      public Zakaznik Zakaznik
      {
            get { return _zakaznik; }
            set { _zakaznik = value; }
      }
      public MyRepeaterItem(Zakaznik zakaznik)
      {
            this._zakaznik = zakaznik;
      }
}
Odkazy na související články

Mozilla: nefunguje window.navigate(‚url‘)

Mozilla navigate(‚url‘) nepodporuje.

Místo toho je potřeba použít standardní

window.location.href = 'url';

…což funguje i v IE.

BoundField částečně ignoruje DataFormatString

Problém je v tom, že na BoundField je před formátováním aplikován HtmlEncode. Je to sicereportovaný a uznaný bug (špatně navrženo), nicméně z důvodu zpětné kompatibility budoucích verzí .NET Frameworku to už zřejmě zůstane v této podobě.

BoundField v GridView tedy ve výchozím nastavení nesprávně vyhodnotí DataFormatString u dat, čísel, atp. – například z hodnoty 0,123456 a formátu „aa {0:n1}“ udělá „aa 0,123456“.
Pomůže nastavit HtmlEncode = „false“, tedy vypnout onen problematický HtmlEncode.

<asp:BoundField
   DataField="MyField"
   DataFormatString="{0:n2}"
   HtmlEncode="false"
/>

NUMLOCK při startu Windows / po přihlášení

Spusťte Registry editor a najděte klíč HKEY_CURRENT_USER\Control Panel\Keyboard a nastavte hodnotu InitialKeyboardIndicators na 2. Tímto zapnete NUMLOCK při přihlášení.

[HKEY_CURRENT_USER\Control Panel\Keyboard]
"InitialKeyboardIndicators"=2

Pokud chcete zapnout NUMLOCK již při startu Windows, nastavte v klíči HKEY_USER\.DEFAULT\Control Panel\Keyboard hodnotu InitialKeyboardIndicators na 2.

[HKEY_USER\.DEFAULT\Control Panel\Keyboard]
"InitialKeyboardIndicators"=2

Jinou možností bez použití Registry editoru je prý přihlásit se jako administrátor nebo jako uživatel s administrátorskými právy a zapnout NumLock. Poté stačí restartovat Windows a při dalším startu se bude zapínat automaticky. Mně to ale většinou nepomůže, zabere až změna v registry…