顯示具有 Entity Framework 標籤的文章。 顯示所有文章
顯示具有 Entity Framework 標籤的文章。 顯示所有文章

2014年6月27日 星期五

LINQ 動態查詢 LINQKit PredicateBuilder + Glimpse 效能監測

早在 2013 LINQ 動態查詢 就困擾著我

http://weisnote.blogspot.tw/2013/03/dynamic-linq.html

當時找到 Dynamic LINQ

但是組字串並不是個好法子

(要組字串的話 我何不直接用 Pure T-SQL)





曾經與 LINQ 纏鬥過的朋友都知道 動態查詢 很難搞

尤其是 Or

並沒有什麼內建的法子 可以讓你動態來 Or Or Or

要不就寫死 .Where(x => x.Foo == 'Foo' || x.Bar == 'Bar || x.Goo == 'Goo' || x.Nar == 'Nar'')

要不就 Expression Trees 硬幹 像這樣

靠自己 好自在...才怪

賣鬧啊 Expression Trees 學習曲線 可沒比 Regular Expression 低 (雖然小弟兩樣都沒學好)





如今 2014 了 技術是會進步的!!

(其實應該出來滿久的 只是我沒去玩他)

拜讀於此:

援助教技--IT點滴日誌:http://neo-tech-tw.blogspot.tw/2013/08/linq-predicatebuilder.html

繼續沉睡5000年:http://leaflet-t-h.blogspot.tw/2011/11/linq-predicatebuilder.html

Kelp Code:http://kelp.phate.org/2011/12/linq-to-object-linqkitpredicatebuilder.html





鄭重歡迎 ~ LINQKit http://www.albahari.com/nutshell/linqkit.aspx





LINQKit 裡面應該是有不少妖魔鬼怪的 但是今天只來玩 PredicateBuilder 來搞定 Or Or Or

照官網範例 很簡單就 完成了

靠大神 好爽...正解





其中比較讓人困惑的 可能就是這句

var predicate = PredicateBuilder.False<Products>();

它的完整型別長這樣

Expression<Func<Products, bool>> predicate = PredicateBuilder.False<Products>();

更不懂了?? 其實他就下面這東東

Expression<Func<Products, bool>> predicate = x => false;

至於什麼時候用 PredicateBuilder.True<T> 什麼時候用 PredicateBuilder.False<T> 則是看接起來是什麼結果

簡單來說 如果都是 Or 用 PredicateBuilder.False<T> 如果都是 And 用 PredicateBuilder.True<T>





眼尖的考究帝一定有發現瀏覽器下面有一條鬼東西

那是最近神人傳授的好物 Glimpse

不僅美觀 功能更甚 MiniProfiler

Glimpse 官網http://getglimpse.com/Docs/

怎麼裝就不用講了 Nuget~催落去

真要說有什麼缺點的話 大概就是 螢幕太小很麻煩 因為 他內容很多 非常寬 : P

2014年2月7日 星期五

關於 LINQ to Entities 的時候 MSSQL 相等比較 預設不區分大小寫的問題

其實這個故事並不是發生在使用 LINQ to Entities 的時候

而是在 SSMS 敲 SQL 的時候

當時在 try 舊 DB 資料轉移新 DB 的整合的方法

說時遲那時快

這鬼才 DB 的 account 欄位的資料 竟然有在重複的 但是 key 不同

雖然登入是靠 SSO 的方式 但是 account 相同應該還是無法辨識才對

除了那些真正相同的 account 之外 忽然發現 大小寫不同 但是字串相同 也會被判斷成相同

估狗了一下才發現 原來 MSSQL 預設是沒在管大小寫的

跟啥 定序 之類的鬼東西有關

解法自然也有

1.直接改那個 定序 的設定

2.加上關鍵字 COLLATE Chinese_Taiwan_Stroke_CS_AS

參考







這種 MSSQL 才有的專屬天使

LINQ to Entities 應該絕對是會無視它的

立馬玩一下 果真如此

走訪 估狗 跟 stackoverflow 之後 解法約有二

1. .ToList() 之後再玩一次 因為這時候後就是 C# 物件 就認得大小寫

但是資料多的話肯定是 GG

2. 用之前的 T - SQL 方法 有很多鬼法子可以讓 LINQ to Entities 也吃 T - SQL

但是就跟 ORM 精神說掰掰



目前的選擇是 2 因為 1 讓人覺得實在多此一舉

但是要搞這種家醜不得外揚的東東

通常都會包成一個方法

有朝一日科技進步 或者 牛人出世 之際 再來改做法




雖然理想上我想要包成這種感覺

var q = db.user.UpperOrLowerCaseWhere(x => x.account == "amy");

讓人有種還是 LINQ 的錯覺

但是實際上有許多難點

1. 可能要反射來反射去 我才有辦法抓到 "account" 這個欄位名稱

2. 浪打返回布林 那我也無法抓到 "amy" -.-

3. 最後還是靠 T - SQL 作亂 如果在這行後面在加其他 LINQ 方法 肯定也是 GG





最後選擇擴充 IQueryable<T> 用法如下

var q = db.user.AsQueryable().UpperOrLowerCaseEquals(db"account""amy");

Where() 會轉成 IQueryable<T> 所以不必加 AsQueryable()

var q = db.user
    .Where(x => x.email.Length > 0)
    .UpperOrLowerCaseEquals(db"account""amy");

Extension 大概長這樣

    public static class IQueryableExtension
    {
        public static IEnumerable<TEntity> UpperOrLowerCaseEquals<TEntity>(
            this IQueryable<TEntity> query,
            DbContext db,
            string columnName,
            string keyword
        ) where TEntity : class
        {
            string sql = query.ToString();
            
            IObjectContextAdapter adapter = db as IObjectContextAdapter;
 
            ObjectContext objectContext = adapter.ObjectContext;
 
            var q = objectContext.ExecuteStoreQuery<TEntity>(
                string.Format(@"
                    SELECT * FROM ( {0} ) AS [table] 
                    WHERE [{1}] COLLATE Chinese_Taiwan_Stroke_CS_AS = @keyword ",
                    sql,
                    columnName
                ),
                new SqlParameter("@keyword"keyword)
            );
 
            return q.ToList();
        }
    }




命名為 UpperOrLowerCaseEquals 而非 UpperOrLowerCaseWhere

則是限制只用在相等比較

WHERE 要搞定的東東或許太複雜 ˊ_>ˋ



最後...牛人快出世吧

2013年8月21日 星期三

效能大戰番外編 - 性格乖僻扭曲的傢伙 為了摘一朵花 翻越整個青藏高原 之 使用 Expression Trees (運算式樹狀架構) 動態 操作 LINQ 消除重複程式片段 這真令人心情莫名興奮愉悅

標題是想模仿 這個 的說 但是好像還是不夠長





如果你是個無法忍受任何原因任何方式出現相同 OR 相似重複程式碼片段 性格乖僻扭曲的傢伙的話

當你在玩 LINQ + entity framework 的時候 一定遇過這個困擾

        public void Kill(int id)
        {
            using (TransactionScope tran = new TransactionScope())
            {
                UserInfo UserInfo = db.UserInfo.FirstOrDefault(x => x.UserID == id);
                if (UserInfo != null)
                {
                    db.UserInfo.Remove(UserInfo);
                }
                UserAccount userAccount = db.UserAccount.FirstOrDefault(x => x.UserID == id);
                if (userAccount != null)
                {
                    db.UserAccount.Remove(userAccount);
                }
                db.SaveChanges();
                tran.Complete();
            }

        }

如果你覺得這段非常自然毫無異常 恭喜 你不是個 性格乖僻扭曲的傢伙





性格乖僻扭曲的傢伙 會說 如果今天這樣怎辦

重複片段 不能接受啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊~

當下決定 反射吧!男孩

        public void Kill(int id)
        {
            using (TransactionScope tran = new TransactionScope())
            {
                dynamic DbSetArr = new dynamic[]
                {
                    db.UserInfo,
                    db.UserAccount
                };
                foreach (var dbSet in DbSetArr)
                {
                    RemoveByUserID(id, "UserID", dbSet);
                }
                db.SaveChanges();
                tran.Complete();
            }
        }

        private void RemoveByUserID<T>(int id, string p, DbSet<T> dbSet) where T : class
        {
            T target = dbSet.FirstOrDefault(
                x => (int)x.GetType()
                    .GetProperty("UserID")
                    .GetValue(dbSet) == id
            );
            if (target != null)
            {
                dbSet.Remove(target);
            }

        }

you usually right, but this time you are left

你通常是正確的 但是這次不行

沒錯 LINQ To Entity 有後天性功能缺乏症候群 你沒辦法在 FirstOrDefault() 這類 LINQ 方法內 搞些卑鄙手段





就在不信邪試盡各種伎倆鼻青臉腫之後 果斷決定 stackoverflow 上的各位 借給我一點元氣吧!!!

        public void Kill(int id)
        {
            using (TransactionScope tran = new TransactionScope())
            {
                DbSet[] DbSetArr = new DbSet[]
                {
                    db.UserInfo,
                    db.UserAccount
                };
                foreach (var dbSet in DbSetArr)
                {
                    RemoveByUserID(id, "UserID", dbSet);
                }
                db.SaveChanges();
                tran.Complete();
            }
        }

        private void RemoveByUserID(int id, string columnName, DbSet dbSet)
        {
            Type type = dbSet.GetType().GetGenericArguments()[0];
            ParameterExpression parameter = Expression.Parameter(type, "x");
            //x.ColumnName
            MemberExpression left = Expression.Property(parameter, columnName);
            //id (Constant Value)
            ConstantExpression right = Expression.Constant(id);
            //x.ColumnName == id
            BinaryExpression filter = Expression.Equal(left, right);
            //x => x.ColumnName == id
            LambdaExpression firstOrDefaultMetod = Expression.Lambda(filter, parameter);
            MethodCallExpression resultExp = Expression.Call(
                typeof(Queryable),
                "FirstOrDefault",
                new Type[] { type },
                dbSet.AsQueryable().Expression,
                Expression.Quote(firstOrDefaultMetod)
            );
            object target = dbSet.AsQueryable().Provider.Execute(resultExp);
            if (target != null)
            {
                dbSet.Remove(target);
            }

        }




太讚了 I don't know WTF is it, but it works









----工作人員清單----













----番外編的番外編----

我們都知道 泛型 是利於泛用 而非利於重用

所以如果上面的方法是個泛型方法...

        public void Kill(int id)
        {
            using (TransactionScope tran = new TransactionScope())
            {
                RemoveByUserID<UserInfo>(id, "UserID", db.UserInfo);
                RemoveByUserID<UserAccount>(id, "UserID", db.UserAccount);
                db.SaveChanges();
                tran.Complete();
            }
        }

        private void RemoveByUserID<T>(int id, string columnName, DbSet<T> dbSet) where T : class
        {
            ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
            T target = dbSet.AsQueryable().Provider.Execute<T>(Expression.Call(
                typeof(Queryable),
                "FirstOrDefault",
                new Type[] { typeof(T) },
                dbSet.AsQueryable().Expression,
                Expression.Quote(
                    Expression.Lambda(
                        Expression.Equal(
                            Expression.Property(parameter, columnName),
                            Expression.Constant(id)
                        ),
                        parameter
                    )
                )
            ));
            if (target != null)
            {
                dbSet.Remove(target);
            }

        }

效果自然是一樣 但是由於 泛型的限制

RemoveByUserID<UserInfo>(id, "UserID", db.UserInfo);

RemoveByUserID<UserAccount>(id, "UserID", db.UserAccount);

這段變得無法跑 foreach






這時候就能用反射囉 (請注意是能用 但是這不代表這是個好方法 過度使用反射未必會得到好結果 ._.)

        public void Kill(int id)
        {
            using (TransactionScope tran = new TransactionScope())
            {
                object[] DbSetArr = new object[]
                {
                    db.UserInfo,
                    db.UserAccount
                };
                foreach (var dbSet in DbSetArr)
                {
                    RemoveByUserID(id, dbSet);
                }
                db.SaveChanges();
                tran.Complete();
            }
        }

        private void RemoveByUserID(int id, object dbSet)
        {
            Type type = dbSet.GetType().GetGenericArguments()[0];
            //此處必須加上 BindingFlags 才能反射到 private method
            MethodInfo removeMethod = this.GetType().GetMethod(
                "Remove",
                BindingFlags.NonPublic | BindingFlags.Instance
            ).MakeGenericMethod(type);
            removeMethod.Invoke(this, new object[] { id, "UserID", dbSet });
        }

        private void Remove<T>(int id, string columnName, DbSet<T> dbSet) where T : class
        {
            Type type = typeof(T);
            ParameterExpression parameter = Expression.Parameter(type, "x");
            //x.ColumnName
            MemberExpression left = Expression.Property(parameter, columnName);
            //x.ColumnName 7
            ConstantExpression right = Expression.Constant(id);
            //x.ColumnName == 7
            BinaryExpression filter = Expression.Equal(left, right);
            //x => x.ColumnName == 7
            LambdaExpression firstOrDefaultMetod = Expression.Lambda(filter, parameter);
            MethodCallExpression resultExp = Expression.Call(
                typeof(Queryable),
                "FirstOrDefault",
                new Type[] { type },
                dbSet.AsQueryable().Expression,
                Expression.Quote(firstOrDefaultMetod)
            );
            T target = dbSet.AsQueryable().Provider.Execute<T>(resultExp);
            if (target != null)
            {
                dbSet.Remove(target);
            }

        }