程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> Linq To Sql進階系列(七)動態查詢續及CLR與SQL在某些細節上的差別

Linq To Sql進階系列(七)動態查詢續及CLR與SQL在某些細節上的差別

編輯:關於.NET

在上面一篇文章Linq To Sql進階系列(六)中,我們提到了使用object的動態查詢。本文在上文的 基礎上,再做更加深入的引申。同時修正上文中一些不妥的地方。

1, object的動態查詢續

首先要做的事情,就是將Find的函數改成擴展方法。擴展方法只能放在靜態類裡,而且它的第一個參 數必須帶this關鍵字。在上文中,作者留下了一個迷題。當需要or條件時,又該如何做呢?本文也將這 個問題給出回答。但是對於動態Like的條件,筆者依然還沒有找到一個較好的方法。為了增加or條件, 函數的聲明也再一次被改動。如下:

public static IQueryable<TEntity> Find<TEntity>(this IQueryable<TEntity> source, TEntity obj, bool isAnd) where TEntity : class

在上文中,我們還碰到了System.Nullable<int>此類類型不支持的問題。其實這個地方主要原 因在於我們構造right端的Expression Tree時,沒有給它參數。那麼這個問題通過Expression right = Expression.Constant(p.GetValue(obj, null), p.PropertyType); 可以得到修復。那整個函數修改後 ,如下:

public static IQueryable<TEntity> Find<TEntity>(this IQueryable<TEntity> source, TEntity obj, bool isAnd) where TEntity : class
{
        if (source == null)
            throw new ArgumentNullException("Source can't be null!!");
        //獲得所有property的信息
        PropertyInfo[] properties = obj.GetType().GetProperties (BindingFlags.Public | BindingFlags.Instance);

        Expression condition = null;
        //先構造了一個ParameterExpression對象,這裡的c,就是Lambda表達中的參數。( c=>) 
        //本變量被移出了foreach循環
        ParameterExpression param = Expression.Parameter(typeof(TEntity), "c");
        //遍歷每個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;
                            }
                        }
                        //構造表達式的右邊,值的一邊
                        Expression right = Expression.Constant (p.GetValue(obj, null), p.PropertyType);
                        //構造表達式的左邊,property一端。
                        Expression left = Expression.Property (param, p.Name);
                        //生成篩選表達式。即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;

}

在這裡,首先檢查輸入的參數是否為null。擴展方法其實是按靜態方法執行的。它和靜態方法唯一不 同的就是系統自動為其加了一個Attribute,而這個Attribute只能通過在第一個參數加this關鍵字才能 獲得。而後,在影射類型上,修改後的函數只支持數值型和string型。其原因就是像imager等並不支持 條件查詢。為了簡化,我們只支持數值型和string型。這裡最大的變化莫過於支持or條件了。調用 Expression.And或Expression.Or就可以了。還有一個變化就是ParameterExpression對象和 Expression<Func<TEntity, bool>>被移出了foreach循環。這樣,提高了效率,只是在最 後才去生成條件。

而實際上,大家大多使用是and條件,那再重載一個方法。

public static IQueryable<TEntity> Find<TEntity>(this IQueryable<TEntity> source, TEntity obj) where TEntity : class
{
  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 ();

大家可以看到,它們和系統定義方法一樣使用,可以接在任何滿足條件的語句後面。第三個例子直接 就用的static方法的形式。從第三個例子,我們可以看出,extension methods和static methods差別其 實不大。

它們生成的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,限定字段在某集合中

這有點像in操作。比如where city in ('London', 'BeiJing') 也可以寫成 where city = 'London' or city = 'BeiJing'。既然談到or條件的動態構造了,那就也來構造下這個吧。看上去有點多此一舉。 但是,至少是個很好的學習機會。這個和上面不同的是,它條件字段是唯一的,變化的是該字段的值。 那用一string將字段名成傳入,並用一集合將字段值傳入函數。

該函數完整的定義入下:

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

使用時,

var q3 = db.Customers.WhereOr("City", new List<string> { "London", "BeiJing" }).ToList();

並不在多做解釋。

3, CLR與SQL在某些細節上的差別

在上文中,有一朋友提出,其值不為null才做為條件,讓函數有局限性。既然提了,那筆者就再引申 下。CLR與SQL中,對待null值是不同的。CLR認為兩個null值是相等的,而SQL並不這麼認為。比如,下 面的條件就是成立的。

if (null == null)
  throw new Exception("CLR treat Null is the same!!");

但在Sql中只能判斷是不是null值,而不能對兩個字段的null值直接比較。

比如下面的語句

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是通過Ado.Net於Sql打交道的。也就是說Linq To Sql是建立在CLR基礎上的。這點細小 的差別讓Linq To Sql不知道該與誰保持平行。 Where條件中,有 == 和Equal兩個方法,它們在Linq To Sql中是不一樣的。Equal認為null是相等的。但是sql又不能用=來判斷,所以Equal方法翻譯的sql語句 就有些長。請大家自己仔細比較下面兩個語句的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在數值精度上的差別,也常讓CLR拋OverFlow異常.這個很好判斷,如果Ado.Net拋這個異常 了,那Linq To Sql肯定要拋,所以並不是Linq To Sql的問題。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved