軟體開發(軟件開發)

網智數位主要提供套裝及客製化的軟體系統解決方案,專為客戶量身訂做客製化的軟體,達成客製化、智慧化及網路化的管理功能。

室內設計、裝潢、窗簾報價估算軟體

網智數位主要提供套裝及客製化的軟體系統解決方案,針對室內設計師、木工、裝潢業產業,量身訂做客製化的軟體,達成客製化、智慧化及網路化的商用軟體。

商用軟體-客製化設計

網智數位主要提供套裝及客製化的軟體系統解決方案,專為客戶量身訂做客製化的軟體,達成客製化、智慧化及網路化的管理功能。

IOT 物聯網-系統開發

根據客戶實際狀況,結合雲端與載具進行客製化物聯網IOT導入與軟體開發

雲端VPS虛擬主機租用

我們的雲端VPS虛擬主機是採用雲端(虛擬化)技術所開發之全新雲端伺服器服務,可以選擇多種作業系統(Windows、Linux等),客戶可載入自訂的應用環境,執行自己所要提供的網路服務,我們的雲端服務可為您的網站提供最完美的解決方案。

ERP軟體客製化導入

ERP軟體客製化導入,室內設計、營造業、裝潢、木作工程、系統櫃工程、會計系統,全面提升公司管理營運效率。

搜尋引擎最佳化SEO

搜尋引擎最佳化(SEO)不僅能提高網站在搜尋結果的排名,更能帶來大量對我們產品或服務真正有需求的訪客。SEO 最棒的特質之一就是不像廣告一樣亂槍打鳥而導致用戶的反感,反而更能提升點閱率跟成交率喔。

服務宗旨

網智數位主要提供套裝及客製化的軟體系統解決方案,專為客戶量身訂做客製化的軟體,達成客製化、智慧化及網路化的管理功能。

我們的成立宗旨就是要以最猛的IT技術讓這個世界更Smart,在我們貫徹我們裡想的同時,我們希望可以把我們所開發的系統帶給台灣的中小企業,除了要推薦好的東西之外,我們也希望做點改變,所以我們的第一目標就是要使用最好用的系統再加上您寶貴的創意,不僅僅可以節省你大量的荷包,還可以有一個像樣的網站。我們可以幫你做的有

企業管理
  • 策略管理
  • 目標管理
  • 行銷管理
  • 財會管理
  • ERP導入
  • 企業流程自訂
資訊管理
  • 網站架設
  • 虛擬化/雲端架設
  • 主機代管
  • 私有雲建制與導入
軟體開發
  • UML設計
  • 版本控管
  • 企業軟體開發
  • APP開發
  • 網頁設計
資訊安全
  • 網頁弱點掃描
  • 主機弱點掃描
  • 木馬檢測
  • 資安鑑識
  • 設計網路架構
  • 資安監控
行銷
  • 關鍵字SEO
  • 社群網路行銷
  • 部落格行銷
  • FaceBook 粉絲團
其他
  • 協助企業申請Google Email
好玩工具開發

講出你的創意吧!沒有甚麼是資訊辦不到的

2014年11月9日 星期日

[Entity Framework] 效能深入探討(一)

      作者我在使用Entity Framewok開發時,最常聽到的一句話就是,Entity Framework 是否"好像"效能很慢喔,在微軟剛推出Entity Framewok前(事實上微軟真的這方面算是進展很慢,其他程式陣營早就有相關的Framework,例如 Java 的 Hibernate,不過後來真的速度改版的很迅速,微軟都這樣玩法),我會很肯定的回答"是",如果當時要採用 Entity Framewok時,我想可能最大的益處就是可以在程式開發過程時,比較可以使用"概念模型"(Conceptual Model)以及 OOP(物件導向)的思維導向,也可以避免程式開發人員在寫程式盡可能避免了 SQL Script,當然也避免了一些開發人員真的不太會寫 SQL,相信我真的還遇到過,不過隨著Entity Framewok 發展到 Version 6 UP.., 我相信大多數是可以依靠 EF(Entity Framewok),而且開發速度的優勢已經是大多數優先考慮採用的原因,尤其現在到處都是 WEB API、MVC架構(EF歸於 Model  Layer),我們更需要好好學習一下,所以我打算寫幾篇關於 EF效能的底層技術與原理,畢竟 Entity Framewok 雖然把底層 SQL的工作,都幫你處理好,但你知道為何有時似乎效能真的好像還是很慢,所以我想直接跟讀者說一些原理,這也是市面上書籍似乎都沒在探討的.....

     我先在此介紹一個Entity Framework 的術語 Lazy Load , 它代表 延遲載入 預設值是成立(true)  。為了解釋何謂Lazy Load,我自己帶入一個案例來進行解釋
例如在我們 CRM系統裏有 [客戶類型]類別 (CustCate Class) , 而每個[客戶類型]可以有多個興趣的[客戶]類別 (Customer Class),此時很典型存在所謂的一對多 One-To-Many 的模型 ;而每個[客戶]類別也可以記錄此客戶相關的[客戶事件]類別(CustomerEvent Class)
,下面我列出了案例模型圖


根據上述模型我們來實作一個 Code First EF Model 
實作 Customer 、CustCate、CustomerEvent Class Code ....

    [Table("Customer")]
    public partial class Customer
    {
        public Customer()
        {
            CustomerEvent = new HashSet<CustomerEvent>();
        }

        [Key]
        [StringLength(15)]
        public string No { get; set; }

        [StringLength(10)]
        public string CName { get; set; }

        [StringLength(10)]
        public string ENmae { get; set; }

        [StringLength(10)]
        public string CateCode { get; set; }

        public virtual CustomerCate CustomerCate { get; set; }

        public virtual ICollection<CustomerEvent> CustomerEvent { get; set; }
    }

    
    [Table("CustomerCate")]
    public partial class CustomerCate
    {
        public CustomerCate()
        {
            Customer = new HashSet<Customer>();
        }

        [Key]
        [StringLength(10)]
        public string Code { get; set; }

        [Required]
        [StringLength(15)]
        public string Name { get; set; }

        public virtual ICollection<Customer> Customer { get; set; }
    }

    [Table("CustomerEvent")]
    public partial class CustomerEvent
    {
        [Key]
        [StringLength(36)]
        public string EventID { get; set; }

        [StringLength(15)]
        public string CustNo { get; set; }

        [Required]
        [StringLength(300)]
        public string Desc { get; set; }

        public DateTime CreateDate { get; set; }

        public virtual Customer Customer { get; set; }
    }

     
實作一個繼承 DbContext 的 EF 存取容器

    public class CRMEF : DbContext
    {
        public CRMEF()
            : base("name=EF")
        {
        }

        public virtual DbSet<Customer> Customer { get; set; }
        public virtual DbSet<CustomerCate> CustomerCate { get; set; }
        public virtual DbSet<CustomerEvent> CustomerEvent { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Customer>().ToTable("Customer");
            modelBuilder.Entity<CustomerCate>().ToTable("CustomerCate");
            modelBuilder.Entity<CustomerEvent>().ToTable("CustomerEvent");

            modelBuilder.Entity<Customer>().HasMany(e => e.CustomerEvent)
                                           .WithOptional(e => e.Customer)
                                           .HasForeignKey(e => e.CustNo);

            modelBuilder.Entity<CustomerCate>().HasMany(e => e.Customer)
                                               .WithOptional(e => e.CustomerCate)
                                               .HasForeignKey(e => e.CateCode);

            base.OnModelCreating(modelBuilder);
        }
    }


我也設計一個前端界面

在寫入測試資料 Button,先寫入基本資料以利後面的解說
private void btnTestData_Click(object sender, EventArgs e)
{
  try
   {
       using (CRMEF ef = new CRMEF())
        {
             CustomerCate cate1 = new CustomerCate() { Code = "Retail", Name = "零售商" };
             CustomerCate cate2 = new CustomerCate() { Code = "Direct", Name = "直客" };
             CustomerCate cate3 = new CustomerCate() { Code = "EC", Name = "網路消費者" };

             Customer cust1 = new Customer() { CustomerCate = cate1, No = "001", CName = "智慧零售商", ENmae = "Smart" };
             CustomerEvent custEvent1 = new CustomerEvent() { Customer = cust1, EventID = Guid.NewGuid().ToString(), Desc = "詢問新產品目錄", CreateDate = DateTime.Now };
             ef.CustomerEvent.Add(custEvent1);
             CustomerEvent custEvent2 = new CustomerEvent() { Customer = cust1, EventID = Guid.NewGuid().ToString(), Desc = "購買10套 網智數位 CRM 套裝軟體", CreateDate = DateTime.Now };
             ef.CustomerEvent.Add(custEvent2);
             Customer cust2 = new Customer() { CustomerCate = cate2, No = "002", CName = "柯P", ENmae = "柯P" };
             CustomerEvent custEvent3 = new CustomerEvent() { Customer = cust2, EventID = Guid.NewGuid().ToString(), Desc = "尋找物聯網的合作廠商", CreateDate = DateTime.Now };
             ef.CustomerEvent.Add(custEvent3);
             Customer cust3 = new Customer() { CustomerCate = cate3, No = "003", CName = "連勝吻", ENmae = "連勝吻" };
             CustomerEvent custEvent4 = new CustomerEvent() { Customer = cust3, EventID = Guid.NewGuid().ToString(), Desc = "網路行銷推廣", CreateDate = DateTime.Now };
             ef.CustomerEvent.Add(custEvent4);

             ef.SaveChanges();
             MessageBox.Show("Test Insert OK");
         }
    }
    catch { }
 }

執行後,我們可以看到後端資料庫就存在一下資料


現在有了案例的資料後,我就可以開始解釋何謂 Lazy Loading,在上述案例我們如果需要列出所有客戶以及他相關的活動事件,以及此客戶歸屬的類別,我把它寫在 "Lazy Loading" Button Click:

  1. private void btnLazyLoading_Click(object sender, EventArgs e)
  2. {
  3. this.richTextBox1.Clear();
  4. this.richTextBox1.AppendText("使用 Lazy Loading 讀取,系統預設行為! \r\n");
  5. this.richTextBox1.AppendText("------------------------------------------------------------------------- \r\n");

  6. using (CRMEF ef = new CRMEF())
  7. {
  8. var customers = ef.Customer;

  9. foreach (var cust in customers)
  10. {
  11. this.richTextBox1.AppendText("客戶編號 : " + cust.No + " 客戶姓名 :" + cust.ENmae + "\t  類型 :" + cust.CustomerCate.Name + "\r\n");
  12. this.richTextBox1.AppendText("--------------------------------事件記錄------------------------------------ \r\n");

  13. foreach (var evt in cust.CustomerEvent)
  14. {
  15. this.richTextBox1.AppendText(evt.Desc + "\r\n");
  16. }

  17. this.richTextBox1.AppendText("\r\r\n");
  18. }
  19. }
  20. }
執行後結果如下

看樣子結果都是沒錯的,但各位我要告訴你們,你知道 上述結果 Entity Framewok 是幫我們產生多少次 SQL語法嗎? , 以及來回跟後端資料庫查詢幾次嗎?
答案是 7 次 ,也就是說白話一點,就是 EF要來回跟後端資料庫溝通要得資料7次,才可以把我們要呈現的資料,完全取回來,各位如果客戶資料不是現在案例的3筆,而100筆...1000筆....甚至 10000筆以上呢?

我分別把我追蹤後的SQL 語法寫下:
第 1 次 SQL :
SELECT
    [Extent1].[No] AS [No],
    [Extent1].[CName] AS [CName],
    [Extent1].[ENmae] AS [ENmae],
    [Extent1].[CateCode] AS [CateCode]
    FROM [dbo].[Customer] AS [Extent1]

第 2 次 SQL :
exec sp_executesql N'SELECT
    [Extent1].[Code] AS [Code],
    [Extent1].[Name] AS [Name]
    FROM [dbo].[CustomerCate] AS [Extent1]
    WHERE [Extent1].[Code] = @EntityKeyValue1',N'@EntityKeyValue1 nvarchar(10)',@EntityKeyValue1=N'Retail'

第 3 次 SQL :
exec sp_executesql N'SELECT
    [Extent1].[EventID] AS [EventID],
    [Extent1].[CustNo] AS [CustNo],
    [Extent1].[Desc] AS [Desc],
    [Extent1].[CreateDate] AS [CreateDate]
    FROM [dbo].[CustomerEvent] AS [Extent1]
    WHERE [Extent1].[CustNo] = @EntityKeyValue1',N'@EntityKeyValue1 nvarchar(15)',@EntityKeyValue1=N'001'

第 4 次 SQL :
exec sp_executesql N'SELECT
    [Extent1].[Code] AS [Code],
    [Extent1].[Name] AS [Name]
    FROM [dbo].[CustomerCate] AS [Extent1]
    WHERE [Extent1].[Code] = @EntityKeyValue1',N'@EntityKeyValue1 nvarchar(10)',@EntityKeyValue1=N'Direct'

第 5 次 SQL :
exec sp_executesql N'SELECT
    [Extent1].[EventID] AS [EventID],
    [Extent1].[CustNo] AS [CustNo],
    [Extent1].[Desc] AS [Desc],
    [Extent1].[CreateDate] AS [CreateDate]
    FROM [dbo].[CustomerEvent] AS [Extent1]
    WHERE [Extent1].[CustNo] = @EntityKeyValue1',N'@EntityKeyValue1 nvarchar(15)',@EntityKeyValue1=N'002'

第 6 次 SQL :
exec sp_executesql N'SELECT
    [Extent1].[Code] AS [Code],
    [Extent1].[Name] AS [Name]
    FROM [dbo].[CustomerCate] AS [Extent1]
    WHERE [Extent1].[Code] = @EntityKeyValue1',N'@EntityKeyValue1 nvarchar(10)',@EntityKeyValue1=N'EC'

第 7 次 SQL :
exec sp_executesql N'SELECT
    [Extent1].[EventID] AS [EventID],
    [Extent1].[CustNo] AS [CustNo],
    [Extent1].[Desc] AS [Desc],
    [Extent1].[CreateDate] AS [CreateDate]
    FROM [dbo].[CustomerEvent] AS [Extent1]
    WHERE [Extent1].[CustNo] = @EntityKeyValue1',N'@EntityKeyValue1 nvarchar(15)',@EntityKeyValue1=N'003'


這就是 Entitfy Framework 的預設行為 Lazy Loading , 上述 C# 語法我們先列舉了所有客戶的清單,此時 EF 就會產生 第一個 SQL 語法 是讀取後端 [dbo].[Customer] Table 的 SQL ,
而我們再依序根據每筆客戶分別讀取了客戶類型,此時 EF 又會去跟後端資料庫要求讀取 [dbo].[CustomerCate] Table ,所以有幾筆客戶資料,他就會來回跟資料庫讀取 [dbo].[CustomerCate] Table,各位這就是效能瓶頸所在
接下來我們繼續讀取每筆客戶的系統事件,此時 EF 又要懶惰 (Lazy Loading)的再去跟後端資料庫取得  [dbo].[CustomerEvent] 資料表,看吧!,系統如果資料量大一點,這樣大部分人使用 EF (Entitfy Framework) ,一定死了,被客戶抱怨死了,
此時通常工程師或開發人員就會抱怨 Entity Framewok好爛,效能好差,還是用ADO.Net好....這也是我公司工程師常跟我說類似的話,但如果你知道底層的原理,這都是可以操控自如的,完全可以任意操控EF,使它可以不用笨到來來回回奔走,
至於如何做呢? 就讓我留著下一回好好地告訴大家。。。。

2014年11月2日 星期日

【EntityFramework 實戰】 一對多關聯

     這篇教學文件,我想一步一步的教讀者如何利用 EntityFramework 來寫一對多 (One-To-Many) 關聯 Table的程式碼技巧,並且分別利用 Entity SQL
LINQ To SQL  的方式來讀取資料庫。

在實例個案,我們常常會遇到如下的資料表一對多關聯

(SQL 語法)
  1. CREATE TABLE Employee
  2. (
  3.   [No] NVARCHAR( 12) NOT NULL PRIMARY KEY,
  4.   HireDate Datetime not null default(getdate ()),
  5.   Name NVARCHAR( 12) NOT NULL
  6. )

  7. CREATE TABLE EmployeeSalary
  8. (
  9.  EmpNo NVARCHAR(12) NOT NULL,
  10.  PayYear INT NOT NULL,
  11.  PayMonth INT NOT NULL,
  12.  bonus DECIMAL(18,2) NOT NULL,
  13.  constraint PK_EmployeeSalary primary key (EmpNo,PayYear,PayMonth),
  14.  constraint FK_EmployeeSalary foreign key (EmpNo)
  15.       references Employee ([No])
  16. )


          在上述案例,我們可以知道一個員工(Employee)在每年的每個月有可能因為績效達到某些企業設定的條件,因此可以得到應領取的獎金(EmployeeSalary) , 但也有可能某員工為新進員工,所以雖然在 Employee 資料表存在記錄,但 EmplyeeSalary 卻不存在任何記錄,如果此時企業的系統應用要求,列出所有員工(不論是否已有任何獎金),如有獎金也顯示出在前端界面,我們該如何利用 EntityFramework 去實戰這個很普遍到不行的案例呢? 下面我就示範出比較適當的程式技巧。

首先在此我一樣是利用 Code-First Style 來操控 EntityFramework , 並前端界面我利用Windows Form來示範,前端界面圖示如下

在寫入資料 Button ,我先示範利用 EntityFramewok,來寫入所需的測試資料

     1:    private void btnInsert_Click(object sender, EventArgs e)  
     2:          {  
     3:              using (DBEntities entities = new DBEntities())  
     4:              {  
     5:                  Employee emp1 = new Employee()  
     6:                                  {  
     7:                                      No = "001", //員工編號  
     8:                                      HireDate = DateTime.Parse("2014/05/01"), //僱用日期  
     9:                                      Name = "湯姆克魯斯", //員工姓名                                       
    10:                                  };  
    11:                  EmployeeSalary salary1 = new EmployeeSalary()  
    12:                                           {  
    13:                                               bonus = 1000, //獎金  
    14:                                               PayYear = 2014, //年  
    15:                                               PayMonth = 9, //月                                                
    16:                                           };  
    17:                  emp1.EmployeeSalary.Add(salary1);  
    18:     
    19:                  Employee emp2 = new Employee()  
    20:                  {  
    21:                      No = "002", //員工編號  
    22:                      HireDate = DateTime.Parse("2014/06/01"), //僱用日期  
    23:                      Name = "李奧納多", //員工姓名                                       
    24:                  };  
    25:                  EmployeeSalary salary2_1 = new EmployeeSalary()  
    26:                  {  
    27:                      bonus = 1000, //獎金  
    28:                      PayYear = 2014, //年  
    29:                      PayMonth = 9, //月                                                
    30:                  };  
    31:                  emp2.EmployeeSalary.Add(salary2_1);  
    32:                  EmployeeSalary salary2_2 = new EmployeeSalary()  
    33:                  {  
    34:                      bonus = 6000, //獎金  
    35:                      PayYear = 2014, //年  
    36:                      PayMonth = 10, //月                                                
    37:                  };  
    38:                  emp2.EmployeeSalary.Add(salary2_2);  
    39:     
    40:     
    41:                  Employee emp3 = new Employee()  
    42:                  {  
    43:                      No = "003", //員工編號  
    44:                      HireDate = DateTime.Parse("2014/10/01"), //僱用日期  
    45:                      Name = "基諾李維", //員工姓名                                       
    46:                  };  
    47:     
    48:                  entities.Employee.Add(emp1);  
    49:                  entities.Employee.Add(emp2);  
    50:                  entities.Employee.Add(emp3);  
    51:     
    52:                  entities.SaveChanges();  
    53:              }  
    54:          }  

執行寫入資料按鈕後,後端資料庫結果為如下
 

測試資料有了以後,開始我們可以分別利用 Entity SQL 與 LINQ To Entities 兩種技巧來讀取

在 顯示資料(Entity SQL) 按鈕 ,程式如下
  
     1:   private void button1_Click(object sender, EventArgs e)  
     2:          {  
     3:              this.richTextBox1.Clear();  
     4:     
     5:              using(DBEntities entites = new DBEntities())  
     6:              {  
     7:                  var eSQL = @"SELECT A.No,A.Name,A.HireDate,B.PayYear,B.PayMonth,B.bonus  
     8:                                FROM Employee AS A OUTER APPLY A.EmployeeSalary AS B   
     9:                                ORDER BY A.No ";  
    10:                  var allEmpSalaryRecords = ((IObjectContextAdapter)entites).ObjectContext.CreateQuery<DbDataRecord>(eSQL).ToList();  
    11:     
    12:                  this.richTextBox1.AppendText("所有員工與獎金明細表:\r\n");  
    13:                  this.richTextBox1.AppendText("-------------------------------------------------------------------\r\n");  
    14:     
    15:                  //開始讀取  
    16:                  foreach(var empRecord in allEmpSalaryRecords)  
    17:                  {  
    18:                      if(empRecord["bonus"] != DBNull.Value)  
    19:                      {  
    20:                          this.richTextBox1.AppendText( "員工姓名 : " + empRecord["Name"].ToString() + " \r\n");  
    21:                          this.richTextBox1.AppendText("(年/月)  " + empRecord["PayYear"].ToString() + "/" + empRecord["PayMonth"].ToString()   
                                                                           + " 獎金為 " + empRecord["bonus"].ToString() + " \r\n");  
    22:     
    23:                      }  
    24:                      else  
    25:                      {  
    26:                          this.richTextBox1.AppendText("員工姓名 : " + empRecord["Name"].ToString() + " \r\n");  
    27:                          this.richTextBox1.AppendText(" 未有獎金 \r\n");  
    28:                      }  
    29:                  }  
    30:     
    31:              }  
    32:          }  

執行結果如下


在 顯示資料(LINQ) 按鈕 ,程式如下
  1. private void button2_Click(object sender, EventArgs e)
  2. {
  3.      this.richTextBox1.Clear();

  4.      using (DBEntities entities = new DBEntities())
  5.      {
  6.           var allEmpSalaryRecords = (from emp in entities.Employee
  7.                                            from empSalary in emp.EmployeeSalary.DefaultIfEmpty()
  8.                                            orderby emp.No
  9.                                            select new
  10.                                            {
  11.                                                 emp.Name,
  12.                                                 PayYear = (int?)empSalary.PayYear,
  13.                                                 PayMonth = (int?)empSalary.PayMonth,
  14.                                                 Bonus = (decimal?)empSalary.bonus
  15.                                            }).ToList();

  16.           //開始讀取
  17.           foreach (var empRecord in allEmpSalaryRecords)
  18.           {
  19.                if (empRecord.Bonus.HasValue)
  20.                {
  21.                     this.richTextBox1.AppendText("員工姓名 : " + empRecord.Name + " \r\n");
  22.                     this.richTextBox1.AppendText("(年/月)  " + empRecord.PayYear.ToString() + "/" + empRecord.PayMonth.ToString() 
  23.                                                                          + " 獎金為 " + empRecord.Bonus.ToString() + " \r\n");

  24.                }
  25.                else
  26.                {
  27.                     this.richTextBox1.AppendText("員工姓名 : " + empRecord.Name + " \r\n");
  28.                     this.richTextBox1.AppendText(" 未有獎金 \r\n");
  29.                }
  30.           }
  31.      }
  32. }
執行結果


透過以上2種讀取技巧,我追蹤EntityFramework 6都是自動產生以下 SQL 語法
  1. SELECT 
  2.     [Project1].[C1] AS [C1], 
  3.     [Project1].[No] AS [No], 
  4.     [Project1].[Name] AS [Name], 
  5.     [Project1].[HireDate] AS [HireDate], 
  6.     [Project1].[PayYear] AS [PayYear], 
  7.     [Project1].[PayMonth] AS [PayMonth], 
  8.     [Project1].[bonus] AS [bonus]
  9.     FROM ( SELECT 
  10.         [Extent1].[No] AS [No], 
  11.         [Extent1].[HireDate] AS [HireDate], 
  12.         [Extent1].[Name] AS [Name], 
  13.         [Extent2].[PayYear] AS [PayYear], 
  14.         [Extent2].[PayMonth] AS [PayMonth], 
  15.         [Extent2].[bonus] AS [bonus], 
  16.         1 AS [C1]
  17.         FROM  [dbo].[Employee] AS [Extent1]
  18.         LEFT OUTER JOIN [dbo].[EmployeeSalary] AS [Extent2] ON [Extent1].[No] = [Extent2].[EmpNo]
  19.     )  AS [Project1]
  20.     ORDER BY [Project1].[No] ASC

以上 One-To-Many的EntityFramework 6 讀取方式,完整介紹完畢,希望對讀取有所幫忙

2014年10月29日 星期三

Entity Framework 實戰 - Table Per Hierarchy Inheritance (TPH)實作

      今天我想抽空來寫篇關於 .Net EntityFramework 與 資料庫單一資料表(Table)的實作,這次實作機制方法為 Table Per Hierarchy Inheritance(TPH),顧名思義就是在底層資料庫的一個資料表對應了程式的資料模型多個實體,
我直接舉個例子來解釋資料庫與領域模型間的圖型。

資料庫 Table 圖例

然而在系統設計時,領域模型我們採取了一下設計


在上圖系統領域模型中, Media 為父類別 (Parent Class) ,而 Article 與 Video、Photo皆為繼承 Media 父類別的子類別(Child Class)


PS.注意,在 Media 類別,並沒有 Media 資料表的欄位 MediaType , 而 MediaType欄位的值在資料表是用來識別被繼承的子類別是屬於那個型別,
如 MediaType = "Article" , 此時代表為 Article Class ,依次為 MediaType = "Video" 代表為 Video Class ; MediaType = "Photo" 代表為 Photo Class 。

現在我開始撰寫 Media 、Article 、Video、Photo Class Code


因為 我採用 Code-First 的方式去實作 EntityFramework ,所以底下我設計一個 DBEntities Class ,並讓它繼承了 DbContext ,
這樣後續我就可以透過 DBEntitis 去處理後續的實體增修 CRUD。

上述步驟都完成後,接下來可以來測試透過 DBEntities 來進行新增并實際寫入資料表。
在這邊我利用 Windows Form 當做前端界面來實作


而在 寫入(存檔)的  Button  ,我寫了以下程式
    1:          private void button1_Click(object sender, EventArgs e)  
    2:         {  
    3:             //進行存檔
    4:             using(var context = new DBEntities())  
    5:             {  
    6:                 Article article = new Article() { Title = "網智數位行銷手冊秘笈", MediaID = "001" };  
    7:                 context.Media.Add(article);  
    8:   
    9:                 Video video = new Video() { Title = "網智ERP教學影片", MediaID = "002" };  
   10:                 context.Media.Add(video);  
   11:   
   12:                 Photo photo = new Photo() { Title = "網智數位設計", MediaID = "003" };  
   13:                 context.Media.Add(photo);  
   14:   
   15:                 context.SaveChanges();  
   16:   
   17:             }  
   18:         }  

點擊 寫入(存檔)Button後,此時在去後端資料庫檢查,我們可以看到結果如下

此時,資料表正確無誤的寫入 3 筆資料列,並且 MediaType 欄位,也會自動寫入對應的值(Article、Video、Photo)。




網智數位-軟體開發


2014年8月16日 星期六

停止火狐(Firefox)傳送下載檔案資訊給Google



上一篇中有提到目前的 Google Chrome 瀏覽器中其實有內建「安全瀏覽」功能,可以在使用者上網時自動幫用戶阻擋不安全網站、禁止用戶下載惡意程式,所以如果您已經使用Google Chrome瀏覽器一段時間,你可能會發現,當你下載到惡意軟體的時候Google Chrome會跳出警告視窗,並中斷您的下載。


這是因為您下載的檔案資訊會送到Google 安全中心去做比對。當發現您所下載的檔案與Google 惡意軟體列表有吻合,就會跳出警告視窗,並詢問您是否要繼續下載。


對於使用Firefox 瀏覽器的用戶來說,其實相同的功能也Copy到Firefox 上(從版本31以後),不過根據Mozilla's wiki的說法,Firefox 只會傳送執行檔 exe 檔案給Google 做分析。


但是如果你不是Google 的粉絲也不想Firefox 傳送任何資訊給Google 那怎麼辦呢?


其實有兩種方法可以停止Firefox 傳送下載檔案資訊給Google,但要先確認Firefox 的版本要在31以後喔。第一種方式是改變Firefox 內部預設配置的值。要做到這一點,請先打開你的Firefox 瀏覽器,並在網址列上打about:config,並點選I'll be careful, I promise!




在搜尋列上打browser.safebrowsing.appRepURL,在Value的欄位中就會看到Google Safe Browsing的網址了。




只要點選兩下,就會出現Enter String value這個視窗,將裡面的URL給刪除在按OK。




確定Value是空的




這樣就會儲存設定了,並且成功的停用傳送功能。


另外一種停用的方法,一樣要在網址列上打about:preferences


進入喜好設定,選擇安全性(Security)標籤,並且取消勾選 Block reported attack sites 跟 Block reported web forgeries




這樣就大功告成了。


Google 一直以來都決心捍衛所有人的上網安全,所以Virus total才在2012 年九月被 Google 收購,且相繼又提供了Safe Browsing API 等功能,目的就是要減少用戶被駭客入侵的機會。所以除非知道自己在幹嘛或有特殊需求,不然不建議將此功能給停用喔。

2014年8月11日 星期一

如何回報 Google,詐騙、惡意、釣魚網站

如果您有在使用 Google 搜尋引擎、Google 瀏覽器 Chrome,或是防毒軟體,當您瀏覽了嵌入惡意程式、木馬病毒的網頁或詐騙用的釣魚網站….等,通常都會跳出警告訊息給我們。
有了這些安全機制,在加上自己有些資安的防範意識其實基本上被入侵成功的機率就會比較低,可以參考:


不過即使有了萬全的防護,也不能保證100%安全,因為現在的釣魚/詐騙網站、惡意中繼站,型態多變、成長與更新速度非常快,Google 蜘蛛在多分析在快,也很難即時的發現新的惡意網站。

所以Google 提供了一個讓網友回報的一個平台,當您連結到某一個URL的時候,發現那個URL明明就不是Facebook、Google、Hotmail、Yahoo 等網域名稱,但頁面卻長得一模一樣,且 Google 搜尋引擎或 Chrome 瀏覽器上卻沒有明確標示該網站有問題,那就回報 Google 請他們即刻處理與封鎖。





送出後 Google 就會處理你所提交的惡意網站,審核可能會需要一些時間,所以在這段期間內其他人也可能不小心進入惡意網站,可以依照下面的指示來進行幾個你可以做的後續處理。

報表已寄送

感謝您將報表寄給 Google。現在您已經日行一善了,不妨:
  1. 開心享受一下助人的喜悅;因為您的熱心,網路世界才能變得更加美好。
  2. 確定您已將網路瀏覽器升級為最新版本,並且已套用作業系統的最新修補程式。
  3. 參閱下列網站,進一步瞭解可能使您電腦中毒的惡意軟體:Stopbadware.org 。

雖然回報給Google,無法讓這些惡意網站關站,但至少讓有使用 Google 搜尋引擎或 Chrome 的人可以收到警告訊息,避免受害者越來越多,並將傷害降到最低。

2014年8月4日 星期一

WebInspector-免費線上檢測網站安全

網路詐騙案例層疵不窮,最難防的莫過於社交工程(Social Engineering)了,因為社交工程是利用人性的弱點,如好奇恐懼貪心情色信任不在意等,來設計成各式各樣的詐騙手法,讓人防不慎防。

駭客最常利用的就是:

1.新聞事件

天災人禍像是復興空難、高雄氣爆、台北捷運隨機殺人事件 等..

2.新產品或服務的推出

假的iPhone 6 照片曝光透過電子郵件去誘騙受害者點擊。

3.名人的八卦消息

足以毀掉小賈斯汀的影片
李宗瑞XX影片

4.利用朋友的名義

駭客鎖定目標後,會蒐集目標周邊所熟悉的人、事、物,駭客在利目標所信任的人傳送社交工程郵件誘使受害者點擊。

這些郵件不外乎都是使用人性的弱點,來誘使受害者來點擊,進一步的取得控制權,竊取機密資料,植入木馬與後門等。那麼如何有效的防範社交工程郵件呢?可以參閱:


不過這篇只是針對Outlook的設定做初步防範而已,真正有問題是信件裡的附加檔案與連結,所以通常Chris收到一個非開不可的檔案,都會先使用本機的防毒軟體掃毒,在丟到VirusTotal 看看有沒有問題,最後才會將檔案給開啟,可能有人認為這樣也太麻煩了吧,沒錯資安與方便性一定不會是畫上等號的,但其實現在市面上很多程式都有跟VirusTotal 整合,雖然還是麻煩但至少可以輕鬆的上傳檔案掃毒,這部分Chris後續也會一一的介紹。

那如果Chris收到一個非開不可的網址呢? 因不確定對方的網址是不是安全的,所以Chris通常都會使用一些線上工具來檢測網站是否安全,像是 WebInspector 這個網站,它最主要的功能是檢查目標網站是否安全,是否隱藏了可疑或惡意的程式。

Webinspector 是美國公司 Comodo CA 所成立的。除了提供免費的線上偵測服務之外,也有提供付費服務(例如大量網域的檢查與偵測,免費版只能單一網域),它可以檢查網站是否為黑名單(Blacklist Checking)釣魚攻擊(Phishing)惡意軟體(Malware Downloads)驅動下載攻擊(Drive-by-Downloads)蠕蟲攻擊(Worms)後門(Backdoors)特洛伊病毒(Trojans)啟發式病毒(Heuristic Viruses) 以及其它可疑程式或活動。

網站:WebInspector

使用方式非常簡單只要在搜尋列打上網址在按下Start The Scan按鈕,Webinspector 就會即時做掃描

掃描時間會看網站的大小,通常幾分鐘之內就有結果了,如果網站沒有問題會顯示No malicious activity or malware detected等字眼。

相對的如果發現網站異常會出現紅色驚嘆號,及顯示This is a direct link to malware file.



回報為惡意網站Report as Malicious


顯示歷史紀錄Show History


另外他還有WHOIS的功能,可以查看該網域的一些基本資訊


最後 WebInspector 除了可以防範社交工程外,也可以拿WebInspector 來檢視自己的網站有沒有問題,一般Chris也會建議用戶定期的做掃描,避免自己的網站被當做惡意中繼站而不知道不去處理,導至被列入黑名單。

2014年7月28日 星期一

VMware P2V 將實體機器轉成虛擬機器(實體轉虛擬)

在最近幾年來對資訊產業來說『雲端』這一詞可以說是最熱門的話題,不論是什麼都要和『雲端』扯上關係,像是雲OS、辦公室軟體、雲端掃毒軟體、雲儲存、雲端電視、雲端遊戲等,好像現在的服務都一定要加上『雲端』,且現在媒體總是報導著雲端雲端!! 導致越來越多人想一窺雲端的面貌,也越來越多企業緊追雲的蹤跡,無不希望飛上雲端。

延伸閱讀:

當然我們的客戶也不例外,最近一個客戶砸了重金買了軟硬體,為了也是希望他們的系統能夠上雲端,但上雲端之前,系統的虛擬化是一個很重要的一步,如何順利的將公司現有的實體機器,轉換成虛擬主機,Chris將這幾年所遇到的問題、評估與注意事項,用筆記的方式分享給大家,有P2V需求或有興趣的朋友請看下去。

P2V簡介:
實體到虛擬,即 Physical to Virtual 簡稱 P2V。是從實體機複製操作系統、應用程式或者數據到虛擬機的技術。

P2V工具:
VMware vCenter Converter, 免費(後續之內容以此VMware vCenter Converter來做介紹)

軟體:VMware vCenter Converter
使用目的:實體主機虛擬化成虛擬機器檔案

P2V的準備評估事項:
  1. 待轉的機器是否為支援的OS版本
  2. 與AP人員討論check list,有什麼機制去證明機器是轉換完畢的。
  3. 有些特殊卡片、接頭的,VMware無法支援的,可能需要討論是否有變通方法。
  4. 對雲端管理者而言,需討論轉入機器所需要的Port,防火牆與掃毒的機制(如何證明機器不會影響到環境內其他虛擬機)。
  5. 高效能吃資源建議專用
  6. DB鎖license例如sybase
P2V流程中的注意事項:
  1. 均須有最高權限帳號去執行P2V作業。
  2. 從原本IBM & HP 或他牌機器P2V時,若原本有裝原廠的Agent,轉完後建議把這些Agent移除。
  3. 轉完後均須安裝VMware Tools。
  4. VMware vCenter Converter 轉換時,建議用同網段去作,否則常常會因Delay,會有不明錯誤失敗。
  5. 若實體機有分系統槽與資料槽(storage),建議可以只先轉系統槽,資料槽可以用其他方式COPY過去,會使P2V速度加快。
  6. 轉換Windows時,因常有莫名因素,Converter部屬待轉機器時,會遇到無法遠端安裝Converter Agent的狀況,此時手動在待轉機器上安裝Converter Agent即可。
  7. 轉換Linux時,因Converter是用Helper iso去開起虛擬機,並且用SSH的方式去Copy整個資料,所以需要配置一個暫時用的IP。
P2V完成之注意事項
  1. 當轉換完畢後,需要注意IP網段的衝突,轉換Linux時更需注意。
  2. AP的驗證與測試,依照Check list逐一驗證。
  3. 是否有一些不必要的安裝程式可以刪除,例如一些Server原廠的Agent。(windows)一些隱藏的驅動程式(網卡)需要刪除,以免IP咬住。
支援版本(沒有出現在以下之OS,代表不支援)

Windows

Linux

最後VMware vCenter Converter的使用方式其實很簡單,這邊就不再額外截圖教學了,最重要的是上述所說的方法,如果該注意的地方都有注意,基本上轉換成功的機率就很大,不過實際上每個人的環境都不一樣,所遇到的問題也會不同,無法一一列出注意事項,如果有甚麼漏掉,或是有遇到甚麼奇怪的問題,都歡迎大家來討論。

2014年7月22日 星期二

[SEO]透過關鍵字也能找到您網站中的圖片



一般 Allen 在撰寫部落格文章,整篇文章通常都是以文字文主,圖片為輔盡量都用淺顯易懂的字句來表達,這樣對搜尋引擎來說是分常友善的,這個大家都知道,不過不一定每個站長都是同樣的狀況來表達,有的時候遇到的情況是需要大量圖片來做說明,例如購物車或教學等.....但圖片就不能SEO嗎? 其實是可以的。

那麼圖片要如何SEO呢?下面幾點供大家參考





1.圖檔名稱

以前Allen在Y拍上賣過東西,用數位相機所拍攝商品圖片的檔名,一般是長「DSCF1234.JPG」或是「2014-07-22 23.23.23.jpg」,等無意義的英文數字或是按照時間日期所組成的流水號,即使你的SEO做得很好也不會有人會去搜尋這樣的關鍵字的。所以圖檔的檔名請加上關鍵字或是品牌名稱,例如:今天有一個商品名稱是KITTY推車墊,那圖片名稱我就會改成kitty-babycar-01.jpg,記住!關鍵字間請用「-」隔開而不是用底線「_」。

2.圖片所在頁面

圖片和所在頁面的主題需相符合,以上面的例子來說好了介紹KITTY推車墊的頁面若放上美食的圖片,怎麼想都不覺得這兩者會有甚麼關係,以使用者的立場來說是一個很不好的使用者體驗,對人是這樣對搜尋引擎也是一樣的。

3.多使用文字說明

在一篇文章中,通常我們會插入一些圖片,來輔助文字敘述說明的不足。而在購物網站中,圖片似乎就變成了主角,而文字就變成加強說明圖片無法呈現的資訊,如:商品資訊、商品規格。所以在圖片旁適時的置入具有關鍵字的「文字」說明,可千萬不要把說明文字都做到圖片裡哦!

4.替代文字 ALT

在圖片加入替代文字加入關鍵字是最重要的一環,當圖片由於一些原因不能夠顯示的時候,alt屬性使您可以指定供替代顯示的文字。 在這裏我們的alt文本是一句簡短而正確的對圖片的描述,描述文字千萬不要太長,以購物網站來說最快的做法就是把商品的商品標題整串複製再貼到主圖的 ALT,這樣的作法就是在告訴搜尋引擎這張圖是什麼東西。

它的語法格式長的像這樣:<img src="http://www.netqna.com/seo.jpg" alt="keyword" / >

結語

其實圖片優化還有其它可執行的部份,不過!我們上述的重點先做,其它的部份我們再逐漸去補足,執行SEO時不需要一次做足避免過度優化,讓網站慢慢成長是搜尋引擎喜歡的模式。

2014年7月12日 星期六

免費版 WAF ModSecurity 增加網站安全性

在大部分成功被駭客入侵的網站案例中,大多都屬於網頁程式設計師的疏忽,或本身缺乏安全的意識而開發的軟體,很容易造成網頁主機被入侵成功的危機,如果不確定自己有沒有被入侵,可以參考我之前寫的5個自我檢視資料庫之安全性的方法,先自行檢測看看。

一般為了預防像是 XSS、SQL Injection 此類的安全性漏洞,大多會購買WAF(Web Application Firewall,網頁應用程式防火牆)來阻絕駭客,但類似的產品,價格往往相當的昂貴,絕非一般中小企業所能負擔。 相關 WAF 資訊可以參考:


或許有人會說為什麼要砸大錢買WAF?WAF不過是至標不治本的東西,程式本身有漏洞應該是將漏洞修補起來就好了,當然這是正確的觀念,如果你的維護的是小系統可能還可以迅速做修補,但如果是大型的 ERP 系統在現實上就沒有那麼簡單了,而且在修補的過程中沒有任何防護的話,還是有一段的空窗期喔。

為了獲得最好的安全效果,我們需要雙管齊下,一方面必須提高管理者和開發者的安全意識,另一方面儘可能提高應用系統的安全性。

因此,本文將介紹這款開放原始碼的軟體 ModSecurity,免費為您的網站主機多加一層保護。

ModSecurity 簡介

ModSecurity是一個開放原始碼的WAF(Web Application Firewall,網頁應用程式防火牆),是一個入侵偵測與防護引擎,它可以作為你的服務器基礎安全設施,目前支援Apache、IIS、Nginx 等..可增強這些 Web 伺服器的安全性和保護Web應用程式避免遭受來自已知與未知的攻擊。

ModSecurity功能特點

  1. 即時監控和攻擊檢測
  2. 攻擊防禦和即時修補
  3. 靈活的規則引擎
  4. 嵌入式模式部署
  5. 基於網絡的部署
  6. 可移植性

ModSecurity新特性


  1. 增加狀態報告(Status Reporting)
  2. 增加JSON解析器
  3. 添加@detectXSS模塊
  4. 連接限制(SecConnReadStateLimit/SecConnWriteStateLimit)支持黑白名單
  5. 增加新變量FULL_REQUEST和FULL_REQUEST_LENGTH,支持對請求的所有內容進行規則限制。
簡單的說 ModSecurity 可保護您的 Web 應用程式免受複雜的攻擊,阻止線上身份竊取,並防止資料經由應用程式洩露,建議系統管理者都可以安裝這個防護措施。

2014年6月28日 星期六

使用Dual Stack同時擁有IPv4與IPv6 速度變慢(IPv6 Fallback)的解決方法

可能很多人沒有關注或發現,其實早在幾年前 IPv4 的位置就已經配發完了,也就是說未來我們架設的網站或Services,會逐漸面臨到如何與IPv6網路共存的問題,而最近遇到了一些客戶已經未雨綢繆地籌備IPv4到IPv6的過渡方法。

目前IPv4到IPv6的過渡方法有下列三種
  1. Dual Stack(IPv4/IPv6雙堆疊)
  2. Tunneling(隧道)
  3. NAT-PT(轉換)
而我們其中一個客戶選擇使用第一種Dual Stack(IPv4/IPv6雙堆疊)的方式來做轉換,使用這種方式算是相當的簡單,只要電腦或是網路上的路由器同時支援IPv4和IPv6即可,對於公司行號內部來說轉換相當容易,只需將網路節點換成支援Dual Stack的裝置即可,每個裝置同時擁有IPv4和IPv6位址,2種網路同時並存在公司內部,卻又不相互干擾。不過當我們的客戶使用 Dual Stack 上線沒有多久,他們的使用者卻抱怨上Facebook變的很慢很慢(迷之聲..上班可以玩臉書嗎? XDDD),一開始用戶懷疑是DNS解析以及同時擁有IPv4與IPv6 是不是有回覆的優先順序的問題,這邊我說明一下,DNS Server 會依用戶詢問的記錄(Domain name record)回覆,當某個Domain name record只有IPv4位址,就只回覆IPv4位址當某個Domain name record只有IPv6位址,就只回覆IPv6位址如果某個Domain name record有IPv4及IP6位址,則IPv4、及IPv6位址都回覆,所以沒有先後順序問題。

後來請用戶的網管人員調閱Firewall Log發現IPv6的連線都被Deny掉了,原來用戶的網管人員沒有設定IPv6的Firewall Policy,但用戶為什麼還是可以連到Facebook呢?原因是現在新的OS,一般都會先嘗試連線IPv6位址,而如果該Domain name record  的IPv6連線有問題,或是該Domain name record設定有問題,會造成OS繼續嘗試IPv6位址,大約在經過21秒後才改嘗試該Domain name record的IPv4位址,所以使用者雖然可以連上Facebook但很緩慢,這就是IPv6 Fallback現象。

所以要解決這個問題有兩種解決方法,第一個解決方法比較簡單,就是請網管人員開通Firewall Policy,第二種就是設定OS可透過下面教學,來控制OS本身IPv4、IPv6路由的優先順序,來避開這個問題。

Windows 設定方式

Windows: (::ffff:0:0即是指IPv4),此指令輸入後是永久生效,不必每次都重新設定。

C:\>netsh interface ipv6 show prefixpolicies
正在查詢使用中的狀態...

優先順序    標籤   首碼
----------  -----  --------------------------------
        50      0  ::1/128
        40      1  ::/0
        30      2  2002::/16
        20      3  ::/96
        10      4  ::ffff:0:0/96
         5      5  2001::/32


C:\>netsh interface ipv6 set prefixpolicy ::ffff:0:0/96 100 4
確定。


C:\>netsh interface ipv6 show prefixpolicies
正在查詢使用中的狀態...

優先順序    標籤   首碼
----------  -----  --------------------------------
       100      4  ::ffff:0:0/96
        50      0  ::1/128
        40      1  ::/0
        30      2  2002::/16
        20      3  ::/96
         5      5  2001::/32

C:\>

Linux 設定方式

Linux: (::ffff:0:0即是指IPv4),以下對/etc/gai.conf檔加入即可。
最後在做一個重開機。

~]# cat /etc/gai.conf
# Label
# Add another rule to the RFC 3484 label table. See section 2.1 in
# RFC 3484.
# The default is:
label ::1/128 0
label ::/0 1
label 2002::/16 2
label ::/96 3
label ::ffff:0:0/96 4
label fec0::/10 5
label fc00::/7 6

#
# Precedence
# Add another rule the to RFC 3484 precedence table. See section 2.1
# and 10.3 in RFC 3484.
# The default is:
precedence ::1/128 50
precedence ::/0 40
precedence 2002::/16 30
precedence ::/96 20
#precedence ::ffff:0:0/96 10
#
# For sites which prefer IPv4 connections change the last line to
precedence ::ffff:0:0/96 100
~]# shutdown -r now