星期五, 5月 09, 2008

當anonymous type碰上class

話說..linq這技術已經跑了一陣子了,我還沒有跟上這好用的新技術(是人家說的),這好像給它真的有點遜,所以就著手把手上自己的class去把一些關於資料庫的部份由ado轉成linq去跑,不過也是至這樣,一開始"馬上"就遇上問題(阿不是說"馬上就"好..騙人)..

一般我們寫class常常都會用到return,不管是傳回string int array datatable等等這些行型態的物件,想當然爾這些class改過後當然還是會傳回來"某"些東西,這是問題就大啦,聽我徐徐道來,首先就是Anonymous Type這東東,先看原始碼

public class test1
{
public var getObj()
{
linqToSqlDataContext db = new linqToSqlDataContext();
var tb = from a in db.Product
select a;
return tb;
}
}

上面原始碼一定會過不去,因為AnonymousType只能用在"區域"不能隨便亂跑,您可以參考這裡
http://msdn.microsoft.com/en-us/library/bb384061.aspx
所以當然要解決問題,解決的方法還蠻多種的,我就拿我這例子在直接繼續下去

public class test1
{
public IQueryable<Product> getObj()
{
linqToSqlDataContext db = new linqToSqlDataContext();
IQueryable<Product> tb = from a in Products
select a;
return tb;
}
}

感謝天阿,可以用了ㄟ,一切都是這麼自然這麼簡單..爽..
不過台語俗諺說得好:搖擺沒有落魄得久,攏是相遇得到的,我馬上就給它遇到..一﹏一||..
馬上看這例子..

var tb = from a in Products
select new
{
a.ProductName,
a.UnitPrice,
a.UnitsInStock
};

這裡因為我們用new的方法去改變原本的結構,所以傳回來會Anonymous Type,所以所以呢..要準備投降ㄇ,當然沒有,goolge永遠是我們最好的老師,查了一下網路上也是有人遇到相同的問題..

方法1
public class test1
{
public IQueryable getObj()
{
linqToSqlDataContext db = new linqToSqlDataContext();
var tb = from a in Products
select new
{
a.ProductName,
a.UnitPrice,
a.UnitsInStock
};
return tb.AsQueryable();
}
}

這方法是用AsQueryable()去把它轉型成IQueryable然後就可以傳來給它傳去,不過缺點是轉了以後很多事情都不能做,就像Kobe Bryant不會切入拉杆Steve Nash不會傳球助一樣,一整個很冏..

456

所以如果你傳回去後沒有要在做啥事情,例如直接bind()這類的話,用這就行啦..

方法2
public class test1
{
public IQueryable<newTb> getObj()
{
linqToSqlDataContext db = new linqToSqlDataContext();
IQueryable<newTb> tb = from a in Products
select new newTb
{
ProductName = a.ProductName,
UnitPrice = a.UnitPrice,
UnitsInStock = a.UnitsInStock
};
return tb;
}
}
public class newTb
{
public string ProductName { get; set; }
public decimal? UnitPrice { get; set; }
public short? UnitsInStock { get; set; }
}

我們多建立一組class然後去滿足它輸出的格式,這種作法就像O/R Designer幫我們做class一樣的道理,所以才有很方便的intellisense..

789

不過也是有缺點的,因為很麻煩,要自己再去建立class,對於我這懶人如果欄位一多,我看我還是去睡覺算了,真是俗語說的"有一好沒兩好"..冏..

方法3
這是一個想法,如果我能把回傳值變成datatable那是不是可以用linq to AOD.NET這些方法去處理,這是個好想法(老王賣瓜自賣自誇..哈哈),馬上就想到CopyToDataTable()東東,不過看到msdn裡的定義,心又涼了一半

public static DataTable CopyToDataTable<T>(
this IEnumerable<T> source
) where T : DataRow

哇勒..機車鬼只能用在datarow裡,那這裡不就啥鬼都不能用了..冏..
話說"山窮水盡疑無路柳暗花明又一村",我又找到我的"村"啦,msdn線上有解決方案,它又擴充了CopyToDataTable()的方法,不在只有datarow可以用其他的型別也照吃,只要照他的方法把擴充的class放到專案中就行了,class在這邊http://msdn.microsoft.com/en-us/library/bb669096.aspx自行下載服用,不過這extension還有一個問題就是遇上nullable types就會死,所以改掉這一段就行了..

if (!_ordinalMap.ContainsKey(p.Name))
{
// Add the property as a column in the table if it doesn't exist
// already.
DataColumn dc = table.Columns.Contains(p.Name) ? table.Columns[p.Name]
: table.Columns.Add(p.Name, p.PropertyType);

// Add the property to the ordinal map.
_ordinalMap.Add(p.Name, dc.Ordinal);
}

換成..

if (!_ordinalMap.ContainsKey(p.Name))
{
Type colType = p.PropertyType;
if ((colType.IsGenericType) && (colType.GetGenericTypeDefinition() == typeof(Nullable<>)))
{
colType = colType.GetGenericArguments()[0];
}

DataColumn dc = table.Columns.Contains(p.Name) ? table.Columns[p.Name]
: table.Columns.Add(p.Name, colType);
_ordinalMap.Add(p.Name, dc.Ordinal);
}

所以這樣大致就解決我們遇到的問題..看原始碼ㄅ..

public class test1
{
public DataTable getObj()
{
linqToSqlDataContext db = new linqToSqlDataContext();
var tb = from a in db.Products
select new
{
a.ProductName,
a.UnitPrice,
a.UnitsInStock
};
return tb.CopyToDataTable();
}
}

輸出引用

DataTable aa = new test1().getObj();
foreach (DataRow a in aa.Rows)
{
Response.Write(a.Field<string>("ProductName"));
Response.Write(a.Field<decimal?>("UnitPrice").Value);
Response.Write(a.Field<short?>("UnitsInStock").Value);
}

呼呼~繞了一圈轉了一圈,終於得到了我們想要的結果,上面的三種方法任君挑選,喜歡用哪個就用哪個,以彈性來說當然首推第二種跟第三種,沒有哪一種方法比較好,就"順手"就好,framework 3.5提供了Anonymous Type這個很方便的東西,不過帶來的後遺症就是你根本不知道現在型別是啥ㄇ,然後傻傻的用遇到問題就傻住愣住不知道該怎麼辦,微軟想給你方便你也別隨便給它用下去,用下去之前最好知道在幹啥麼這樣以後遇到問題還好解決

ps:呼呼~終於寫完..好累喔..

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2825401&SiteID=1

沒有留言: