2017年11月24日 星期五

toaster 技巧

讓滑鼠停在裡面時不會取消計時
<toaster-container toaster-options="{'mouseover-timer-stop':false}"></toaster-container>

背景不要透明
#toast-container > div {
    opacity: 1 !important;
}

2017年11月16日 星期四

使用 linq + lambda + entity framework 查詢資料效能緩慢

1. 避免先撈出資料放在陣列中再進行比對
錯誤範例
var 沖銷支票號碼 = db.傳票.SelectMany(a=>a.傳票明細).Where(a => a.類別 == "K3" && a.借貸 == "1").Select(a => a.支票號碼).ToList();
var list=db.應付票據.Where(a => !沖銷支票號碼.Contains(a.支票號碼));
改良寫法
var list=db.應付票據.Where(a => !db.傳票.Any(b=>b.傳票明細.Any(c => c.類別 == "K3" && c.借貸 == "1" && c.支票號碼==a.支票號碼));

2. 資料量少且存在會重複讀取的資料的時候先 tolist 再 select 會比直接select 快,否則避免先 tolist
資料量少且傳票明細會重複讀取
db.傳票.Select(a => new { 傳票= a, 明細s = a.傳票明細 }).ToList().Select(a => new { id = a.傳票.id, 借方合計 = a.明細s.DefaultIfEmpty().Sum(b => b.借方金額), 貸方合計 = a.明細s.DefaultIfEmpty().Sum(b => b.貸方金額) })
資料量多
db.傳票.Select(a => new { id = a.傳票.id, 借方合計 = a.傳票明細.DefaultIfEmpty().Sum(b => b.借方金額), 貸方合計 = a.傳票明細.DefaultIfEmpty().Sum(b => b.貸方金額) })

3. DefaultIfEmpty() 效能不佳,且用法有限制(某些情況會造成執行錯誤), 請改用 ToList()

4. 避免 select 中使用到無法直接轉成 sql 語法的指令,避免執行時自動tolist再select造成效能緩慢
ex:
public enum enum類別   { 進貨驗收, 進貨退回,}
list = db.進貨驗收明細.Where(a =>... ).Select(a => new { 類別 = enum類別.進貨驗收 }).ToList(); // enum類別.進貨驗收 無法轉換成sql 語法
             
5. 查詢結果一律轉為list,否則之後使用時會重複執行查詢指令產生結果,造成效能低落
var aaa=db.訂單.Where(a=>...).Select(a =>...).ToList();
foreach (var a in aaa) 
...
var bbb=aaa.Where(a=>...); // 若不先tolist 會重新查詢產生aaa

6. 透過 partial class 另外存取關聯資料時,不要直接存取,必須重頭查詢,因為直接存取時會先把關聯資料表全部從資料庫抓到記憶體,造成效能下降,資料越多越明顯
舉例說明:
public partial class 訂單 {
  public bool 包含3C => 訂單明細.Any(b =>b.類別 == "3C"); // 會先把"訂單明細"所有資料抓到記憶體,再找出該訂單相關的明細
}
請改為以下標準寫法
public bool 包含3C(Entities db)
{
  return db.訂單明細.Any(b =>b.訂單編號==訂單編號 && b.類別 == "3C");
}

7. 查詢明細資料直接查詢,不要先查詢主檔再用 .selectmany() 取得明細

8. groupby 之前先把需要的欄位抓出來,且之後盡量不要再用 .FirstOrDefault() 去抓欄位,可以納入 groupby 就納入

9. .find() 會把資料全部都先抓到記憶體,且會自動呼叫DetectChanges (參考這裡),若頻繁呼叫或資料量大(例如用到image 型態儲存圖片)則改用 .where()+.select()+.single() 只抓需要的欄位,或設定AutoDetectChangesEnabled 為 false,或用 .single(a=>a.id==?)

10. 避免這樣處理 : select 分兩次,第一次抓需要的欄位,第二次針對欄位做其他運算 (例如sum)

11. 不要這樣寫 : (產品進出明細s ?? db.產品進出明細).Where(a=>...)

12. 盡量避免把陣列很大的資料透過 sql server 判斷是否存在其中,而是先抓出資料後在記憶體中比對,且不要用 Any() 否則會造成 stackoverflow (對應 sql 的 in 固有限制?)
db.aaa.Where(a => !bbb.Contains(a.id)).ToList(); 
db.aaa.Select(a => a.id).ToList().Except(bbb).ToList(); // 如果只需要單一欄位值
or
db.aaa.Where(a => !bbb.Contains(a.id)).ToList(); 
db.aaa.Where(a => xxx).ToList().Where(a => !bbb.Contains(a.id)).Select(a=>new {xxx}).ToList(); // 需要抓多個欄位,把 contains 放在 memory 中處理

13. 有時查詢條件會跨多個table,造成效能下降,不用table join改用view 會改善

14. 有時會遇到select 關聯table 效能低落甚至 timeout,必須在記憶體中使用二次查詢來處理關聯table,原因不明,若兩個table 資料量都很大可能會遇到
(await db.aaa.Select(a=>new {a.name,a.id}).ToListAsync()).Select(async a=>new {a.name,a.id,list=await db.bbb.Where(b=>b.pid==a.id).Select(b=>new {b.name,b.id}).ToListAsync()}).Select(a=>a.Result)

2017年11月15日 星期三

無法處理型別 '匿名型別',因為它沒有值層的已知對應

這屬於 entityframework concat 用法錯誤
tolist()  和沒有 tolist() 進行 concat,其中一個集合為空 就會拋錯
應統一選擇是否 tolist() 然後再 concat

2017年11月9日 星期四

透過 directive 控制對齊方式

參考現成js : https://gist.github.com/egermano/7451739
或直接把directive 放入自己的 module

.directive('align', function ($timeout) {
        'use strict';

        var linker = function (scope, element, attrs) {
            var options = attrs['align'].split(" "),
                listner = attrs['alignWatch'];

            var positioner = function () {
                angular.forEach(options, function (value, key) {

                    element.css('position', 'absolute');

                    switch (value) {
                        case 'top':
                            element.css({
                                top: '0',
                                marginTop: '0'
                            });
                            break;
                        case 'bottom':
                            element.css({
                                bottom: '0',
                                marginBottom: '0'
                            });
                            break;
                        case 'middle':
                            element.css({
                                top: '50%',
                                marginTop: ((element.height() / 2) * -1) + 'px'
                            });
                            break;
                        case 'right':
                            element.css({
                                right: '0',
                                marginRight: '0'
                            });
                            break;
                        case 'left':
                            element.css({
                                left: '0',
                                marginLeft: '0'
                            });
                            break;
                        case 'center':
                            element.css({
                                left: '50%',
                                marginLeft: ((element.width() / 2) * -1) + 'px'
                            });
                            break;
                    }
                });
            }

            $(window).resize(function () {
                positioner();
            });

            if (listner) {
                scope.$watch(listner, function () {
                    positioner();
                });
            };

            $timeout(function () {
                positioner();
            });
        };

        return {
            restrict: 'A',
            link: linker
        };
    })

2017年11月8日 星期三

轉型為 'System.Decimal' 型別失敗,因為具體化的值為 Null。此結果型別的泛型參數或此查詢,兩者中必須有一個使用可為 Null 的型別

若查詢結果沒有資料,直接Sum 會拋這個錯誤 (某種寫法才發生)

先 DefaultIfEmpty() 再 Sum() 可避免此問題 <= 效能差
Ex:
db.財會系統_帳戶資料期初.Where(a => a.公司id == id && a.id == 帳戶id && a.年度 == 期初日期.Year).DefaultIfEmpty().Sum(a => a.期初金額 ?? 0)

若先 ToList() 再 Sum() 可以避免錯誤,但查詢結果沒有資料時則結果會不如預期
Ex:
var list = db.傳票明細.Where(b => b.傳票.公司id == 公司id && b.沖帳發生 == true && b.金額 - b.沖銷.Where(c => c.沖帳發生 != true).ToList().Sum(c => c.金額) > 0)
b.沖銷.Where(c => c.沖帳發生 != true) 沒有資料的不會列出,推測是 b.沖銷.Where(c => c.沖帳發生 != true).ToList().Sum(c => c.金額) 被當作 null 處理
改用如下寫法則結果就會如預期
var list = db.傳票明細.Where(b => b.傳票.公司id == 公司id && b.沖帳發生 == true && b.金額 - (b.沖銷.Where(c => c.沖帳發生 != true).Sum(c => c.金額)??0) > 0)
若 c.金額 型態不可為null,則先強制轉型
var list = db.傳票明細.Where(b => b.傳票.公司id == 公司id && b.沖帳發生 == true && b.金額 - (b.沖銷.Where(c => c.沖帳發生 != true).Sum(c =>(decimal?)c.金額)??0) > 0)

input 連結 datalist 用程式控制彈出選項

範例: nextTick(() => document.querySelector('input').showPicker());  ※僅支援現代瀏覽器