星期五, 8月 31, 2012

SQL Server Database Project

今天為大家介紹一下 Visual Studio 2102 2012 SQL Server Database Project,不知道大家有沒有用過這個 Database 專案,這個東西相當好用,如果搭配上版本控管工具,可以把我們對於 Database 的 Schema 修改都記錄起來,這樣對於專案的控管都會有所依據,所以直接看圖說故事吧!

開啟專案

clip_image001

開完專案後,我們把我們的 Database 匯進來了

clip_image002

一開始並沒有 Database 連線可以給我們選擇,我們只要去新建就好

clip_image003

新建的方式很簡單,該填的填一填

clip_image004

這樣我們選用剛剛的 Database 連線,如果要把"權限"還有"Database 設定"都匯進來,那把下面的兩個選項都打勾

clip_image005

噹噹噹,我們就可以看到 Database 的資料都匯進來了

clip_image006

看一下匯進來的東西,這樣初始化就完成了

clip_image007

接下來介紹一下 Schema Compare 這個功能,這功能可以比較"來源"與"目標" Schema 的不同,這樣說好像沒啥感覺,舉個常會用到的例子好了,例如我在 Database Server 上改了 Schema,要如何運用這功能來達到比較與同步的效果,看圖繼續說故事吧!
開一個新的 Schema Comparison

clip_image008

設定好"來源"跟"目的",按 Compare 按鈕

clip_image009

由比較就可以看到哪個地方改掉了,如果確定要同步,按下 Update 按鈕就行了,一切都這麼自然,這樣兩邊的 Schema 就同步了,通常 Schema 同步後,Bibby 我都會上版本控管,這樣就可以去追蹤哪個版本修改了哪些欄位哪些內容

clip_image010

不過 Database Project 功能不只是這樣而已,其實我們也可以反向過來做處理,例如我們今天把方案總管的 dbo -> Table -> Test.sql 打開,我們就可以看到下面這個圖形化介面,我們可以直接做處理,例如把某個欄位刪掉,欄位改名等..

clip_image011

我們改完後,把我們之前設定的"來源"跟"目的"反向,因為比較要用 Database Project 的來當"來源",按下 Compare 後就可以比較出來,兩邊的不同,如果要同步,也可以用之前的方式,按下 Update 按鈕就可以了,不過有時候有個問題,我們不一定都可以碰到 Dasebase Server,所以改個方式按下 Generate Script 按鈕,產出 Script ,讓可以動 Database Server 的人執行

clip_image012

這就是產出來的 Script

clip_image013

不過 Bibby 在使用個時候沒有這麼順利,有遇到一個錯誤訊息"A project which specifies SQL Server 2012 as the target platform cannot be published to SQL Server 2008."這個的意思是說,我的 SQL Server 不是 2012 版本,被嫌棄了XD,要解決的方法只能很委屈的去條 vs 2012 的設定…

clip_image014
image

基本運用大概就這些,Bibby 我在開發的時候都有測試的DB,開發了一段時間後,要上正式機時,就把自己的 Database Project 恢復到上次上正式機之前的版本,然後比較一下測試的DB,這樣就可以產出這次要上正式機的 Script 來,用這方式就不怕自己開發的這一段時間 DB 部分改了多少 Schema 啦,大概就是這樣了,有問題再一起討論吧!

Reference:
http://social.msdn.microsoft.com/Forums/en-US/ssdt/thread/312ed73e-f70d-4072-ab2b-77d417dbfee5
http://blog.nhaslam.com/2012/08/20/vs2012-schema-comparison/

星期四, 8月 16, 2012

整合 NLog 到 Elmah

在專案上,log 功能一直是每個專案都要必備的東西,log 做的好,系統問題煩惱少,在 log 功能實作方面,Bibby 我是選用 thrid party 元件 NLog 來處理我需要的功能,NLog 的好用不用我多說,可以參考好友 Kevin一系列文章,介紹非常詳細了,看完了就可以成為 NLog 達人了,那今天要來說啥呢?當然是有特別的需求啦,Nlog 使用上方便,很自由也很詳細,不過問題是要如何很快速的看到 NLog 所記錄的內容,這點比較困擾我,所以問了身旁的一些好朋友,看有沒有比較不錯的方法,好朋友 Dino 給我一些建議說可以參考一下這個連結,看完了這個連結後,又不小心看到 debug 使用的 Elmah 顯示畫面,心理馬上有個譜了..那就雙璧合一水乳交融來一下吧!

我們直接實作開始,Bibby 我這邊 Elmah 是使用 SQLite 來處理的(參考這篇),所以這邊我的 NLog 也會以 SQLite 為主,不過我還是把 MSSQL 的寫好了,如果要用就把註解拿掉就行了(我是好心人 XD)..

第一步,實作 TargetWithLayout

[Target("Elmah")]
public sealed class ElmahTarget : TargetWithLayout
{
    static ElmahTarget()
    {
        ConfigurationItemFactory.Default.Targets
                .RegisterDefinition("Elmah", typeof(ElmahTarget));
    }
 
    public ElmahTarget()
    {
    }
 
    public string ConnectionStringName { get; set; }
 
    protected override void Write(LogEventInfo logEvent)
    {
        var connectionString = 
            ConfigurationManager.ConnectionStrings[ConnectionStringName].ConnectionString;
 
        using (var connection = new SQLiteConnection(connectionString))
        {
            connection.Open();
 
            var application = System.Web.Hosting.HostingEnvironment.ApplicationID;
            var time = logEvent.TimeStamp.ToUniversalTime();
            var eventData = from p in logEvent.GetType().GetProperties()
                            where p.PropertyType.IsValueType || p.PropertyType.IsEnum
                            select new XElement("item",
                                                new XAttribute("name", p.Name),
                                                new XElement("value",
                                                    new XAttribute("string",
                                                        p.GetValue(logEvent, null).ToString())));
            var variables = from k in HttpContext.Current.Request.ServerVariables.AllKeys
                            let v = HttpContext.Current.Request.ServerVariables[k]
                            select new XElement("item",
                                                new XAttribute("name", k),
                                                new XElement("value",
                                                                new XAttribute("string", v)));
            var xdoc = new XElement("error",
                                    new XAttribute("host", System.Environment.MachineName),
                                    new XAttribute("type", logEvent.Exception == null ?
                                                            "" : logEvent.Exception.ToString()),
                                    new XAttribute("message", logEvent.FormattedMessage),
                                    new XAttribute("source", logEvent.LoggerName),
                                    new XAttribute("detail", logEvent.StackTrace == null ?
                                                            "" : logEvent.StackTrace.ToString()),
                                    new XAttribute("time", time.ToString("s") + "Z"),
                                    new XElement("eventData", eventData),
                                    new XElement("serverVariables", variables));
            var user = string.Empty;
            if (HttpContext.Current != null && HttpContext.Current.User.Identity.IsAuthenticated)
            {
                user = HttpContext.Current.User.Identity.Name;
            }
 
            var SQL = @"
insert into [error]
(Application,Host,Type,Source,Message,User,AllXml,StatusCode,TimeUtc)
values
(@Application,@Host,@Type,@Source,@Message,@User,@AllXml,@StatusCode,@TimeUtc)
";
            var cmd = new SQLiteCommand(SQL, connection);
            cmd.CommandType = CommandType.Text;
            //cmd.Parameters.Add("@ErrorId", DbType.).Value = Guid.NewGuid();
            //cmd.Parameters.Add("@ErrorId", DbType.Int32).Value = errorId;
            cmd.Parameters.Add("@Application", DbType.String).Value = application;
            cmd.Parameters.Add("@Host", DbType.String).Value = System.Environment.MachineName;
            cmd.Parameters.Add("@Type", DbType.String).Value = "NLog-" + logEvent.Level.Name;
            cmd.Parameters.Add("@Source", DbType.String).Value = logEvent.LoggerName;
            cmd.Parameters.Add("@Message", DbType.String).Value = logEvent.FormattedMessage;
            cmd.Parameters.Add("@User", DbType.String).Value = user;
            cmd.Parameters.Add("@AllXml", DbType.String).Value = xdoc.ToString();
            cmd.Parameters.Add("@StatusCode", DbType.Int32).Value = 0;
            cmd.Parameters.Add("@TimeUtc", DbType.DateTime).Value = time;
 
            cmd.ExecuteNonQuery();
        }
 
    }
 
    //for ms-sql
    //protected override void Write(LogEventInfo logEvent)
    //{
    //    var connectionString = 
    //          ConfigurationManager.ConnectionStrings[ConnectionStringName].ConnectionString;
        //using (var connection = new SqlConnection(connectionString))
        //{
        //    connection.Open();
 
        //    var application = System.Web.Hosting.HostingEnvironment.ApplicationID;
 
        //    var time = logEvent.TimeStamp.ToUniversalTime();
 
 
        //    var eventData = from p in logEvent.GetType().GetProperties()
        //                    where p.PropertyType.IsValueType || p.PropertyType.IsEnum
        //                    select new XElement("item",
        //                                        new XAttribute("name", p.Name),
        //                                        new XElement("value",
        //                                                     new XAttribute("string", 
        //                                       p.GetValue(logEvent, null).ToString())));
 
        //    var variables = from k in HttpContext.Current.Request.ServerVariables.AllKeys
        //                    let v = HttpContext.Current.Request.ServerVariables[k]
        //                    select new XElement("item",
        //                                        new XAttribute("name", k),
        //                                        new XElement("value",
        //                                                     new XAttribute("string", v)));
 
 
 
        //    var xdoc = new XElement("error",
        //                            new XAttribute("host", System.Environment.MachineName),
        //                            new XAttribute("type", logEvent.Exception == null ?
        //                                              "" : logEvent.Exception.ToString()),
        //                            new XAttribute("message", logEvent.FormattedMessage),
        //                            new XAttribute("source", logEvent.LoggerName),
        //                            new XAttribute("detail", logEvent.StackTrace == null ?
        //                                                      "" : logEvent.StackTrace.ToString()),
        //                            new XAttribute("time", time.ToString("s") + "Z"),
        //                            new XElement("eventData", eventData),
        //                            new XElement("serverVariables", variables));
 
        //    var user = string.Empty;
 
        //    if (HttpContext.Current != null && HttpContext.Current.User.Identity.IsAuthenticated)
        //    {
        //        user = HttpContext.Current.User.Identity.Name;
        //    }
 
        //    var cmd = new SqlCommand("ELMAH_LogError", connection);
        //    cmd.CommandType = CommandType.StoredProcedure;
        //    cmd.Parameters.Add("ErrorId", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid();
        //    cmd.Parameters.Add("Application", SqlDbType.NVarChar).Value = application;
        //    cmd.Parameters.Add("Host", SqlDbType.NVarChar).Value = System.Environment.MachineName;
        //    cmd.Parameters.Add("Type", SqlDbType.NVarChar).Value = "NLog-" + logEvent.Level.Name;
        //    cmd.Parameters.Add("Source", SqlDbType.NVarChar).Value = logEvent.LoggerName;
        //    cmd.Parameters.Add("Message", SqlDbType.NVarChar).Value = logEvent.FormattedMessage;
        //    cmd.Parameters.Add("User", SqlDbType.NVarChar).Value = user;
        //    cmd.Parameters.Add("AllXml", SqlDbType.NVarChar).Value = xdoc.ToString();
        //    cmd.Parameters.Add("StatusCode", SqlDbType.Int).Value = 0;
        //    cmd.Parameters.Add("TimeUtc", SqlDbType.DateTime).Value = time;
 
        //    cmd.ExecuteNonQuery();
        //}
    //} 
}

第二步,在 NLog 的設定檔改設定

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <extensions>
    <add assembly="NlogElmah" />
  </extensions>
  
  <targets>
    <target name="elmah" xsi:type="Elmah" connectionStringName="elmahDB" />
  </targets>
 
  <rules>
    <logger name="*" minlevel="Trace" writeTo="elmah" />
  </rules>
</nlog>

這樣就大功完成了,NLog 的設定檔也非常簡單,沒有啥需要設定的,因為都寫在 code 裡面了 XD…

我們來看一下結果,看是不是有如想像中的結果..

clip_image001
clip_image002

很好,如我所想像的,這樣可以很簡單的去把 NLog 的結果讀出來,有問題再一起討論吧..^^..收工!!

參考:
http://nlog-project.org/wiki/How_to_write_a_Target
http://bibby.be/2011/11/bibbyelmahmssqlmssqlxmlxmlxmlxmlelmahsq.html

星期二, 8月 07, 2012

分頁

最近看到專案,裡面的分頁都是用內建的方法來做分頁(資料都塞到 viewstate),每個 grid 抓出來的資料都是上百筆甚至還有上千筆的,效能都是干我屁事,完全沒有考慮品質的問題,把責任都丟給了區網,還有那傳來傳去累到死 browser 跟 iis,如果在資料少的情況下,這樣寫還可以說得過去,如果資料很多那就是管他媽媽嫁給誰的您老師咖好的寫法..

不過換個想法,在 asp.net 裡面,不把資料塞在 viewstate,要多少資料拿多少資料的分頁寫法,很困難嗎?不然大家怎麼會用這個"內建"的寫法,去處理大量資料的問題,所以當然要股溝谷歌大神 survey 一下,看看 asp.net 有啥好的解決方案

[C#] 使用 ObjectDataSource 實作資料來源分頁
[SubText心得]ASP.NET TSQL 效能較佳分頁方法及友善 SEO 的分頁

上面是網路找到的效率好的分頁處理方式,可以參考一下,不過好像不是這麼簡單,在印象中應該不會這麼困難,所以 Bibby 我就自己做做看,看到底分頁可不可以簡單寫!

第一步,懶惰是程式人的進步動力,知道站在巨人的肩膀上,才會飛的高飛的遠(懶就懶都是多說低),所以就去找了一個 thrid party 的分頁元件,可以用 nuget 來下載

AspNetPager paging control
http://en.webdiyer.com/AspNetPager

clip_image001

第二步,把元件加入 Toolbox Panel

clip_image002

clip_image003

clip_image004

clip_image005

clip_image006

第三步,直接 coding 啦,這裡我是用 northwind.mdb 和 entityframework 來做範例

.aspx(把控制項拉到頁面上)

<%@ Page Language="C#" AutoEventWireup="true" 
CodeBehind="Pager.aspx.cs" Inherits="WebApplication1.Pager" %>


<%@ Register Assembly="AspNetPager" Namespace="Wuqi.Webdiyer" TagPrefix="webdiyer" %>

<!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></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:GridView ID="GridView1" runat="server">
    </asp:GridView>
    <webdiyer:AspNetPager ID="AspNetPager1" runat="server">
    </webdiyer:AspNetPager>
    </div>
    </form>
   
</body>
</html>


.cs

    public partial class Pager : System.Web.UI.Page
    {
        private int PageSize { get { return 8; } }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            AspNetPager1.PageChanged += new EventHandler(AspNetPager1_PageChanged);
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
                InitForm();
        }

        void AspNetPager1_PageChanged(object sender, EventArgs e)
        {
            var pager = sender as AspNetPager;
            BindData(pager.CurrentPageIndex);
        }

        private void InitForm()
        {
            BindData(1);
        }

        private void BindData(int currentIndex)
        {
            //GridView
            GridView1.DataSource = GetGridViewData(currentIndex);
            GridView1.DataBind();

            //Pager
            AspNetPager1.RecordCount = GetAllData().Count();
            AspNetPager1.CurrentPageIndex = currentIndex;
            AspNetPager1.PageSize = PageSize;
        }

        private List<Products> GetGridViewData(int currentIndex)
        {
            int cIndex = currentIndex - 1;
            return GetAllData().OrderBy(a => a.ProductID)
                             .Skip(cIndex * PageSize)
                             .Take(PageSize).ToList();
        }

        private IQueryable<Products> GetAllData()
        {
            NorthwindEntities db = new NorthwindEntities();
            return db.Products;
        }
    }


畫面

clip_image007

這是到這邊,很初步的寫完了,其實 code 並沒有很多,這寫法的優點是,把分頁跟 grid 處理分開了,也沒有牽扯到 store procedure,所以哪一天你覺得 gridview 不好用,想換成 listview 或是 datalist 就直接換掉,沒有啥需要好考慮的,如果想要用 ado.net 來做到相同的效果,應該也不會有太大的問題,只是換換 method 而已,所以..該會的還是要會,品質該顧的還是要顧,出來混的總有一天要還的!!

PS:這 AspNetPager paging control 的控制項有一堆可以調的東西,可以調的很漂亮,範例醜醜的大家忍耐一下

參考:
http://en.webdiyer.com/AspNetPager
http://en.webdiyer.com/AspNetPagerDemo/