顯示具有 LINQ 標籤的文章。 顯示所有文章
顯示具有 LINQ 標籤的文章。 顯示所有文章

2015年2月25日 星期三

關於 Linq To Xml 抓不到 XElement 的問題

Linq To Xml 很強大 用來處理 XML 十分方便

但是下面這個 RSS feed 卻無法順利抓到 XElement

http://www.google.com.tw/alerts/feeds/09781140062556884779/15827306603345874523





說穿了沒什麼 就是它的 XName 實際上叫 "{http://www.w3.org/2005/Atom}entry" 而非 瀏覽器上看到的 "entry"

但是第一次遇到的當下肯定覺得

X! 之前這樣寫明明就可以抓到 怎麼現在不行

著實惱人

stackoverflow





參考寫法:

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年12月31日 星期二

LINQ 封裝 "computed" properties 五行封印術 - 看技術文章學英文第二彈 - 跨年特別篇


警告:此篇為翻譯文章,所有內容皆為原作者所有
此外歡迎指正破英文,如果哪邊有嚴重謬誤哪肯定是我把靈魂出賣給了 google 小姐 ... 這篇好難翻呀 我日 -.-




警告:此篇文章 內容艱澀 技術過硬
這篇 100% 是技術人員寫的 英文用字的 可讀性 可維護性 就跟"大部分的專案" 一樣好




這篇文章的

server 指得是 SQL SERVER 這類的東東

client 指得是 自定義的 C# 代碼

translation 指得是 將 C# 轉成 查詢端語法





客戶端 自訂屬性 和 遠端 LINQ provider


如果你寫了些自訂屬性 你將無法在 遠端 LINQ 操作中 使用他們 (EX:LINQ to Entities)

問題發生的原因在於 這些客製屬性無法 轉譯到 server (EX:SQL SERVER)

因為他們已經被編譯成 中介語言 (intermediate language - IL)

而不是 個 需要被 IQueryable 實作 translation 的 LINQ 表達式樹 (LINQ expression trees)

目前在 .NET 是沒法度 用些反向工程 將 IL 變回方法 (methods) 和 語法 (syntax)

讓我們可以如預期的轉換成查詢語法 (remote query)

這意味者你不得不將查詢寫成兩個部分

首先是 server (EX:SQL SERVER) 可以幹的 (LINQ 查詢)

再用 ToList 或者 AsEnumerable 查詢出中間結果 到 client

然後就只能在這邊進行些運算操作

如果你想要 激烈的 做些轉換 這會非常耗損效能

我們 (David, Colin Meek and myself) 想出了一個 非關 provider 一次性宣告 屬性的方法

這樣他們就可以在兩種情況下使用

他能在 LINQ to SQL, LINQ to Entities 和任何其他 LINQ 輕易的運算屬性

在 .NET 3.5 SP1 運作良好 :)


使用前



這邊我們 擴充 Employee 類別 增加 Age 和 FullName

我們只需要 名字帶有 "da" 的人

但是為了 做出選取 我們不得不 抓下所有東東 到 client


partial class Employee {
 public string FullName {
  get { return Forename + " " + Surname; }
 }

 public int Age {
  get { return DateTime.Now.Year - BirthDate.Year -
   (((DateTime.Now.Month < BirthDate.Month)
   || DateTime.Now.Month == BirthDate.Month && DateTime.Now.Day < BirthDate.Day) ? 1 : 0));
  }
 }
}
...
var employees = db.Employees.ToList().Where(e => e.FullName.Contains("da")).GroupBy(e => e.Age);


使用後


這邊 使用我們的方式

他發生在 server 端...

而且 LINQ to SQL, LINQ to Entities 都行的通


partial class Employee {
    private static readonly CompiledExpression fullNameExpression
     = DefaultTranslationOf.Property(e => e.FullName).Is(e => e.Forename + " " + e.Surname);
    private static readonly CompiledExpression ageExpression
     = DefaultTranslationOf.Property(e => e.Age).Is(e => DateTime.Now.Year - e.BirthDate.Value.Year - (((DateTime.Now.Month < e.BirthDate.Value.Month) || (DateTime.Now.Month == e.BirthDate.Value.Month && DateTime.Now.Day < e.BirthDate.Value.Day)) ? 1 : 0)));

    public string FullName {
        get { return fullNameExpression.Evaluate(this); }
    }

    public int Age {
        get { return ageExpression.Evaluate(this); }
    }
}
...
var employees = db.Employees.Where(e => e.FullName.Contains("da")).GroupBy(e => e.Age).WithTranslations();


使用注意事項


上述技巧的注意事項是 你需要確保 你的類別 在查詢之前 有初始化 (看看下面的替代方案)

然而 很顯然的 你註冊的表達式(expression) 所指的 屬性(property) 必須能夠被 translated

所以你將需要約束你自己的方法(methods) 和 實作 IQueryable


這裡是一些上述例子的幾個替代方案。


註冊 表達式


你可以像這個例子一樣 在類別本身的註冊屬性

該屬性可以不靠任何反射 (reflection) 就能自行 計算表達式 (evaluate the expressions)

此外 如果 效能考量不是那麼重要的話

你可以在其他地方註冊它們

使用 方法 (methods) 透過反射 動態查看他們的值

例如
...
DefaultTranslationOf.Property(e => e.FullName).Is(e => e.Forename + " " + e.Surname);
var employees = db.Employees.Where(e => e.FullName.Contains("da")).GroupBy(e => e.Age).WithTranslations();
...
partial class Employee {
    public string FullName { get { return DefaultTranslationOf.Evaluate(this, MethodInfo.GetCurrentMethod());} }
}




如果 效能考量很重要

你可以獲得 重複運算的 完整代碼

一次從中介語言中的屬性 然後一次從 translation 的表達示

針對不同情境的不同對應


有時候 你的程式的某些部份 可能想在不同的情境下

使用不同的 translations 或 performance 等等

沒問題!

WithTranslations 方法 通常操作著預設的 translation 對應 (map)

(靠 DefaultTranslationOf 訪問)

但也有另一個使用 TranslationMap 的多載

讓你對付特定的情況

例如
var myTranslationMap = new TranslationMap();
myTranslationMap.Add(e => e.Name, e => e.FirstName + " " + e.LastName);
var results = (from e in db.Employees where e.Name.Contains("martin") select e).WithTranslations(myTranslationMap).ToList();



他是怎麼運作的


CompiledExpression<T, TResult>

我們需要做的第一件事是讓 自訂編寫的「計算」屬性

遠離 IL 然後 變回 表達式樹 (expression trees)

所以我們才能 translate 它們

因為我們也想 在 client 計算他們

所以我們需要在 執行時期 編譯他們

CompiledExpression 只是為了取得 表達式 Func<T, TResult> 而存在

允許編譯和計算 objects 針對不同的編譯版本

ExpressiveExtensions


這個小類別 提供 WithTranslations 擴充方法 和 internal TranslatingVisitor

以利走訪 透過 TranslationMap 實際註冊 Func<T, TResult> expressions 的 property

所以 底層的 LINQ provider 可以處理他

TranslationMap


我們需要有 編譯 表達式的 屬性對應

TranslationMap 為此存在

你可以手動建立一個 TranslationMap

將其傳入 WithTranslations

如果你想要依靠程式 在執行時期建立它們 或者 對不同情境使用不同方法

但是通常你會想用它...

DefaultTranslationOf


這個 helper 類別 讓你針對 default TranslationMap 註冊屬性

我們在 WithTranslations 沒有傳入值的時候使用

它也可以讓你查看 已註冊的屬性

所以你也可以計算他們

即使它耗費了一點反射的效能
public int Age { get { return DefaultTranslationOf.Evaluate(this, MethodInfo.GetCurrentMethod()); } }




有樂!


[)amien











最後...新年快樂 2014

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);
            }

        }