Kamil Kliczbor @ asptip.net

26Jun/090

Cache na przykładzie .NET Pet Shop 4

Ciekawe jest, że jest to przykład dobrego stylu programowania. Między innymi zastosowano tam użycia cache w oparciu o Hashtable. Przeprowadzone testy wykazują, że użycie cache nie do końca przynosi spodziewane rezultaty:

using System;

using System.Data;

using System.Data.SqlClient;

 

namespace ConsoleApplication1

{

   class Program

   {

      static void Main(string[] args) {

 

         int[] lengths = PrepareParamsArray();

 

         DateTime start = DateTime.Now;

         for (var t1 = 0; t1 < lengths.Length; t1++) {

            GetWithCache(lengths[t1]);

         }

         DateTime stop = DateTime.Now;

         TimeSpan test1 = stop - start;

         Console.WriteLine("Test with cache time: {0} ms", test1.TotalMilliseconds);

 

         start = DateTime.Now;

         for (var t2 = 0; t2 < lengths.Length; t2++) {

            GetWithoutCache(lengths[t2]);

         }

         stop = DateTime.Now;

         TimeSpan test2 = stop - start;

         Console.WriteLine("Test without cache time: {0} ms", test2.TotalMilliseconds);

 

         start = DateTime.Now;

         for (var t3 = 0; t3 < lengths.Length; t3++) {

            GetWithCacheLonger(lengths[t3]);

         }

         stop = DateTime.Now;

         TimeSpan test3 = stop - start;

         Console.WriteLine("Test with cache long time: {0} ms", test3.TotalMilliseconds);

 

         start = DateTime.Now;

         for (var t2 = 0; t2 < lengths.Length; t2++) {

            GetWithoutCacheLonger(lengths[t2]);

         }

         stop = DateTime.Now;

         TimeSpan test4 = stop - start;

         Console.WriteLine("Test without cache long time: {0} ms", test4.TotalMilliseconds);

 

         Console.ReadKey();

      }

 

      private static SqlParameter[] GetWithCache(int i) {

         SqlParameter[] parms = TestSqlParams.GetParameters("TEST2" + i);

 

         if (parms == null) {

            parms = Get2Params(i);

            TestSqlParams.StoreParameters("TEST2" + i, parms);

         }

 

         return parms;

      }

 

      private static SqlParameter[] GetWithoutCache(int i) {

         return Get2Params(i);

      }

      

      private static SqlParameter[] GetWithCacheLonger(int i) {

         SqlParameter[] parms = TestSqlParams.GetParameters("TEST10" + i);

 

         if (parms == null) {

            parms = Get10Params(i);

            TestSqlParams.StoreParameters("TEST10" + i, parms);

         }

 

         return parms;

      }

 

      private static SqlParameter[] GetWithoutCacheLonger(int i) {

         return Get10Params(i);

      }

 

      private static SqlParameter[] Get2Params(int i) {

         var parms = new[] {

                    new SqlParameter("@Quantity" + i, SqlDbType.Int),

                    new SqlParameter("@ItemId" + i, SqlDbType.VarChar, 10)};

 

         return parms;

      }

 

      private static SqlParameter[] Get10Params(int i) {

         var parms = new[] {

                    new SqlParameter("@Quantity" + i, SqlDbType.Int),

                    new SqlParameter("@ItemId" + i, SqlDbType.VarChar, 10),

               new SqlParameter("@Desc" + i, SqlDbType.Char, 120),

               new SqlParameter("@Amount" + i, SqlDbType.Int),

               new SqlParameter("@Price" + i, SqlDbType.Money),

               new SqlParameter("@ShipNo" + i, SqlDbType.VarChar, 40),

               new SqlParameter("@ShipPlace" + i, SqlDbType.NChar, 30),

               new SqlParameter("@Available" + i, SqlDbType.Bit),

               new SqlParameter("@CanSellPlace" + i, SqlDbType.NText, 1024),

               new SqlParameter("@Guid" + i, SqlDbType.UniqueIdentifier)

         };

 

         return parms;

      }

 

      public static int[] PrepareParamsArray() {

         const int arrLength = 1000000;

         var parmsLen = new int[arrLength];

 

         for (var i = 0; i < arrLength; i++) {

            parmsLen[i] = i % 45;

         }

         return parmsLen;

      }

 

   }

}

Oraz:

using System;

using System.Collections;

using System.Data.SqlClient;

 

namespace ConsoleApplication1

{

   class TestSqlParams

   {

      private static Hashtable cache = Hashtable.Synchronized(new Hashtable());

 

      public static void StoreParameters(string key, params SqlParameter[] parameters)

      {

         cache[key] = parameters;

      }

      

      public static SqlParameter[] GetParameters(string key)

      {

         var cached = (SqlParameter[]) cache[key];

         if (cached == null) return null;

 

         var cloned = new SqlParameter[cached.Length];

 

         for(int index = 0, cacheLen = cached.Length; index < cacheLen; index++)

         {

            cloned[index] = ((SqlParameter) ((ICloneable) cached[index]).Clone());

         }

 

         return cloned;

      }

   }

}

Wyniki są zadziwniające:
Test with cache time: 3432 ms
Test without cache time: 1070 ms
Test with cache long time: 16982 ms
Test without cache long time: 6349 ms

Filed under: Cache, Wydajność No Comments
23May/090

Bindowanie kontrolki DropDownList

Ostatnio, w sumie bezmyślnie, bindowałem listę przy użyciu obiektów ListItem w pętli. Jednak po krótkim zastanowieniu pomyślałem sobie, że jednak mając jakąś kolekcję generyczną, można by elegancko użyć właściwości: DataSource, DataValueField, oraz DataValueText.
Postanowiłem zrobić mały teścik i oto jego rezultaty:

Kod c#:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Web.UI.WebControls; 

namespace TestBinding
{
   public partial class _Default : System.Web.UI.Page

   {

      public static List<DTO> _list = new List<DTO>();

      public class DTO

      {

         public string CustomerId { get; set; }
         public string ShipName { get; set; }

      }

 

      private readonly DataTable _dataTable = new DataTable();

 

      protected override void OnPreInit(EventArgs e) {

         base.OnPreInit(e);

 

         if (!IsPostBack) {

 

            using (

               SqlConnection connection =

                  new SqlConnection(ConfigurationManager.ConnectionStrings["NorthwindConnectionString"].ToString())) {

               SqlDataAdapter adapter = new SqlDataAdapter("SELECT [CustomerID], [ShipName] FROM [Orders]", connection);

               connection.Open();

               adapter.Fill(_dataTable);

               connection.Close();

            }

 

            foreach (DataRow row in _dataTable.Rows) {

               _list.Add(new DTO { CustomerId = row.ItemArray[0].ToString(), ShipName = row.ItemArray[1].ToString() });

            }

         }

      }

 

      protected void Page_Load(object sender, EventArgs e) {

         //test 1

         DateTime start;

         DateTime stop;

 

         start = DateTime.Now;

         DropDownList1.DataValueField = "CustomerId";

         DropDownList1.DataTextField = "ShipName";

         DropDownList1.DataSource = _list;

         DropDownList1.DataBind();

         stop = DateTime.Now;

         Label1.Text = (stop - start).Milliseconds.ToString();

 

         start = DateTime.Now;

         foreach (DTO dto in _list) {

            DropDownList2.Items.Add(new ListItem(dto.ShipName, dto.CustomerId));

         }

         stop = DateTime.Now;

         Label2.Text = (stop - start).Milliseconds.ToString();

 

         //test 2 - sql

 

         start = DateTime.Now;

         DropDownList3.DataValueField = "CustomerId";

         DropDownList3.DataTextField = "ShipName";

         DropDownList3.DataSource = _dataTable;

         DropDownList3.DataBind();

         stop = DateTime.Now;

         Label3.Text = (stop - start).Milliseconds.ToString();

 

         start = DateTime.Now;

         foreach (DataRow row in _dataTable.Rows) {

            DropDownList4.Items.Add(new ListItem(row.ItemArray[1].ToString(), row.ItemArray[0].ToString()));

         }

         stop = DateTime.Now;

         Label4.Text = (stop - start).Milliseconds.ToString();

      }
   }
}

Kod asp:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="TestBinding._Default" %>

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

   <title>Untitled Page</title>

</head>

<body>

   <form id="form1" runat="server">

   <div>

      Lista generyczna

      <br />

      DataBind() <asp:DropDownList ID="DropDownList1" runat="server">

      </asp:DropDownList>

      <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>

      <br />

      <br />

      ListItem() <asp:DropDownList ID="DropDownList2" runat="server">

      </asp:DropDownList>

      <asp:Label ID="Label2" runat="server" Text="Label"></asp:Label>

      <br />

      <br />

      DataTable

      <br />

      DataBind() <asp:DropDownList ID="DropDownList3" runat="server">

      </asp:DropDownList>

      <asp:Label ID="Label3" runat="server" Text="Label"></asp:Label>

      <br />

      <br />

      ListItem() <asp:DropDownList ID="DropDownList4" runat="server">

      </asp:DropDownList>

      <asp:Label ID="Label4" runat="server" Text="Label"></asp:Label>

      <br />

   </div>

   </form>

</body>

</html>

Dane do bindowania wykorzystałem ze standardowej bazy Northwind, tabela Orders liczyła 830 rekordów.

Jak widać mimo "eleganckiego" podejścia do sprawy, rozwiązanie z DataBind() na liście rozwijanej wypada gorzej pod względem wydajnościowym niż w przypadku populowania listy z użyciem obiektu ListItem.