在上面一篇文章Linq To Sql進(jìn)階系列(六)中,我們提到了使用object的動(dòng)態(tài)查詢。本文在上文的基礎(chǔ)上,再做更加深入的引申。同時(shí)修正上文中一些不妥的地方。
1, object的動(dòng)態(tài)查詢續(xù) 首先要做的事情,就是將Find的函數(shù)改成擴(kuò)展方法。擴(kuò)展方法只能放在靜態(tài)類里,而且它的第一個(gè)參數(shù)必須帶this關(guān)鍵字。在上文中,作者留下了一個(gè)迷題。當(dāng)需要or條件時(shí),又該如何做呢?本文也將這個(gè)問(wèn)題給出回答。但是對(duì)于動(dòng)態(tài)Like的條件,筆者依然還沒(méi)有找到一個(gè)較好的方法。為了增加or條件,函數(shù)的聲明也再一次被改動(dòng)。如下: public static IQueryable<TEntity> Find<TEntity>(this IQueryable<TEntity> source, TEntity obj, bool isAnd) where TEntity : class 在上文中,我們還碰到了System.Nullable<int>此類類型不支持的問(wèn)題。其實(shí)這個(gè)地方主要原因在于我們構(gòu)造right端的Expression Tree時(shí),沒(méi)有給它參數(shù)。那么這個(gè)問(wèn)題通過(guò)Expression right = Expression.Constant(p.GetValue(obj, null), p.PropertyType); 可以得到修復(fù)。那整個(gè)函數(shù)修改后,如下: public static IQueryable<TEntity> Find<TEntity>(this IQueryable<TEntity> source, TEntity obj, bool isAnd) where TEntity : class
在這里,首先檢查輸入的參數(shù)是否為null。擴(kuò)展方法其實(shí)是按靜態(tài)方法執(zhí)行的。它和靜態(tài)方法唯一不同的就是系統(tǒng)自動(dòng)為其加了一個(gè)Attribute,而這個(gè)Attribute只能通過(guò)在第一個(gè)參數(shù)加this關(guān)鍵字才能獲得。而后,在影射類型上,修改后的函數(shù)只支持?jǐn)?shù)值型和string型。其原因就是像imager等并不支持條件查詢。為了簡(jiǎn)化,我們只支持?jǐn)?shù)值型和string型。這里最大的變化莫過(guò)于支持or條件了。調(diào)用Expression.And或Expression.Or就可以了。還有一個(gè)變化就是ParameterExpression對(duì)象和Expression<Func<TEntity, bool>>被移出了foreach循環(huán)。這樣,提高了效率,只是在最后才去生成條件。{ if (source == null) throw new ArgumentNullException("Source can‘t be null!!"); //獲得所有property的信息 PropertyInfo[] properties = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); Expression condition = null; //先構(gòu)造了一個(gè)ParameterExpression對(duì)象,這里的c,就是Lambda表達(dá)中的參數(shù)。(c=>) //本變量被移出了foreach循環(huán) ParameterExpression param = Expression.Parameter(typeof(TEntity), "c"); //遍歷每個(gè)property foreach (PropertyInfo p in properties) { if (p != null) { Type t = p.PropertyType; //只支持value型和string型的影射 if (t.IsValueType || t == typeof(string)) { //如果不為null才算做條件 if (p.GetValue(obj, null) != null) { //SQL Server does not support comparison of TEXT, NTEXT, XML and IMAGE ,etc /**////Only support BigInt,Bit,Char,Decimal,Money,NChar,Real, ///Int,VarChar,SmallMoney,SmallInt,NVarChar,NVarChar(MAX),VarChar(MAX) Attribute attr = Attribute.GetCustomAttribute(p, typeof(ColumnAttribute)); if (attr != null) { string dbType = (attr as ColumnAttribute).DbType; if (dbType.Contains("Text") || dbType.Contains("NText") || dbType.Contains("Xml") || dbType.Contains("Image") || dbType.Contains("Binary") || dbType.Contains("DateTime") || dbType.Contains("sql_variant") || dbType.Contains("rowversion") || dbType.Contains("UniqueIdentifier") || dbType.Contains("VarBinary(MAX)")) { continue; } } //構(gòu)造表達(dá)式的右邊,值的一邊 Expression right = Expression.Constant(p.GetValue(obj, null), p.PropertyType); //構(gòu)造表達(dá)式的左邊,property一端。 Expression left = Expression.Property(param, p.Name); //生成篩選表達(dá)式。即c.CustomerID == "Tom" Expression filter = Expression.Equal(left, right); if (condition == null) { condition = filter; } else { if (isAnd) condition = Expression.And(condition, filter); else condition = Expression.Or(condition, filter); } } } } } if (condition != null) { Expression<Func<TEntity, bool>> pred = Expression.Lambda<Func<TEntity, bool>>(condition, param); return source.Where(pred); } return source; } 而實(shí)際上,大家大多使用是and條件,那再重載一個(gè)方法。 public static IQueryable<TEntity> Find<TEntity>(this IQueryable<TEntity> source, TEntity obj) where TEntity : class
我們?cè)賮?lái)測(cè)試一下{ return Find<TEntity>(source,obj,true); } Northwind db = new Northwind(); db.Log = Console.Out; Customer cu = new Customer { City = "London", Country = "UK" }; var q0 = db.Customers.Find(cu).ToList(); var q1 = db.Customers.OrderBy(c=>c.Country).Find(cu, false).ToList(); var q2 = Extension.Find(db.Customers.OrderBy(c => c.CustomerID), cu).ToList(); 大家可以看到,它們和系統(tǒng)定義方法一樣使用,可以接在任何滿足條件的語(yǔ)句后面。第三個(gè)例子直接就用的static方法的形式。從第三個(gè)例子,我們可以看出,extension methods和static methods差別其實(shí)不大。 它們生成的sql為 SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address
], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] FROM [dbo].[Customers] AS [t0] WHERE ([t0].[City] = @p0) AND ([t0].[Country] = @p1) -- @p0: Input NVarChar (Size = 6; Prec = 0; Scale = 0) [London] -- @p1: Input NVarChar (Size = 2; Prec = 0; Scale = 0) [UK] SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address ], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] FROM [dbo].[Customers] AS [t0] WHERE ([t0].[City] = @p0) OR ([t0].[Country] = @p1) ORDER BY [t0].[Country] -- @p0: Input NVarChar (Size = 6; Prec = 0; Scale = 0) [London] -- @p1: Input NVarChar (Size = 2; Prec = 0; Scale = 0) [UK] SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address ], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] FROM [dbo].[Customers] AS [t0] WHERE ([t0].[City] = @p0) AND ([t0].[Country] = @p1) ORDER BY [t0].[CustomerID] -- @p0: Input NVarChar (Size = 6; Prec = 0; Scale = 0) [London] -- @p1: Input NVarChar (Size = 2; Prec = 0; Scale = 0) [UK] 2,限定字段在某集合中 這有點(diǎn)像in操作。比如where city in (‘London‘, ‘BeiJing‘) 也可以寫(xiě)成 where city = ‘London‘ or city = ‘BeiJing‘。既然談到or條件的動(dòng)態(tài)構(gòu)造了,那就也來(lái)構(gòu)造下這個(gè)吧??瓷先ビ悬c(diǎn)多此一舉。但是,至少是個(gè)很好的學(xué)習(xí)機(jī)會(huì)。這個(gè)和上面不同的是,它條件字段是唯一的,變化的是該字段的值。那用一string將字段名成傳入,并用一集合將字段值傳入函數(shù)。 該函數(shù)完整的定義入下: public static IQueryable<TEntity> WhereOr<TEntity, OrType>(this IQueryable<TEntity> source, string propertyName, IEnumerable<OrType> values) { if (source == null) throw new ArgumentNullException("Source can‘t be null!!"); ParameterExpression param = Expression.Parameter(typeof(TEntity), "p"); Expression left = Expression.Property(param, propertyName); Expression condition = null; foreach (OrType value in values) { Expression filter = Expression.Equal(left, Expression.Constant(value)); if (condition == null) condition = filter; else condition = Expression.Or(condition,filter); } if (condition != null) return source.Where((Expression<Func<TEntity, bool>>)Expression.Lambda(condition, param)); return source; } 使用時(shí), var q3 = db.Customers.WhereOr("City", new List<string> { "London", "BeiJing" }).ToList(); 并不在多做解釋。 3, CLR與SQL在某些細(xì)節(jié)上的差別 在上文中,有一朋友提出,其值不為null才做為條件,讓函數(shù)有局限性。既然提了,那筆者就再引申下。CLR與SQL中,對(duì)待null值是不同的。CLR認(rèn)為兩個(gè)null值是相等的,而SQL并不這么認(rèn)為。比如,下面的條件就是成立的。 if (null == null) throw new Exception("CLR treat Null is the same!!"); 但在Sql中只能判斷是不是null值,而不能對(duì)兩個(gè)字段的null值直接比較。 比如下面的語(yǔ)句 var q6 = db.Employees.Where(c => c.Region == null).ToList(); 翻譯為: SELECT [t0].[EmployeeID], [t0].[LastName], [t0].[FirstName], [t0].[Title], [t0].
[TitleOfCourtesy], [t0].[BirthDate], [t0].[HireDate], [t0].[Address], [t0].[City ], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[HomePhone], [t0].[Ext ension], [t0].[Photo], [t0].[Notes], [t0].[ReportsTo], [t0].[PhotoPath] FROM [dbo].[Employees] AS [t0] WHERE [t0].[Region] IS NULL Linq To Sql是通過(guò)Ado.Net于Sql打交道的。也就是說(shuō)Linq To Sql是建立在CLR基礎(chǔ)上的。這點(diǎn)細(xì)小的差別讓Linq To Sql不知道該與誰(shuí)保持平行。 Where條件中,有 == 和Equal兩個(gè)方法,它們?cè)贚inq To Sql中是不一樣的。Equal認(rèn)為null是相等的。但是sql又不能用=來(lái)判斷,所以Equal方法翻譯的sql語(yǔ)句就有些長(zhǎng)。請(qǐng)大家自己仔細(xì)比較下面兩個(gè)語(yǔ)句的sql差別 var q5 = (from e in db.Employees from o in db.Orders where e.Region == o.ShipRegion select new { e.Region, o }).ToList(); var q6 = (from e in db.Employees from o in db.Orders where Equals(e.Region, o.ShipRegion) select new { e.Region, o }).ToList(); CLR和SQL在數(shù)值精度上的差別,也常讓CLR拋OverFlow異常.這個(gè)很好判斷,如果Ado.Net拋這個(gè)異常了,那Linq To Sql肯定要拋,所以并不是Linq To Sql的問(wèn)題。 本文所提到代碼,請(qǐng)到此下載完整版本. |
|