VS 2010 帶來 EF 4.0 (Model-First、FK、Lazy Loading、T4、POCO) 與 WCF Data Services 4.0 (OData 協議、雙向綁定、分頁、行計數、投影、Blob)。本文以 MyBlog 為例:ASP.NET 前台唯讀瀏覽 + Silverlight 後台編輯,示範從建模到部署的完整流程。
ADO.NET Entity Data Model → 選 Empty Model。BlogBlogID (圖 2)。.edmx.sql 完成建表 (圖 3-4)。.Designer.cs 代碼移至 Model1.cs,修改 .tt 即時重新生成,極大提升擴充彈性。Blog/Post/Tag 僅含自動屬性與導航集合。BlogContext : ObjectContext (圖 9),以 CreateObjectSet<T>() 暴露 ObjectSet<T>。
// Blog.cs / Tag.cs
public virtual List Posts { get; set; }
// Post.cs
public virtual List Tags { get; set; }
EF 運行時代理自動攔截屬性存取觸發載入。注意:WCF Data Services 序列化會觸發遞迴載入,本範例故關閉 Lazy Loading 改用 .Expand() 顯式載入。
public class BlogService : DataService
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Blogs", EntitySetRights.All);
config.SetEntitySetAccessRule("Posts", EntitySetRights.All);
config.SetEntitySetAccessRule("Tags", EntitySetRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
// 分頁示範
config.SetEntitySetPageSize("Posts", 5);
}
}
// 僅公開公開貼文 或 當前用戶擁有的貼文
[QueryInterceptor("Posts")]
public Expression> OnPostQuery()
=> p => p.Public || p.Blog.Owner == HttpContext.Current.User.Identity.Name;
// 僅當前用戶擁有的部落格
[QueryInterceptor("Blogs")]
public Expression> OnBlogQuery()
=> b => b.Owner == HttpContext.Current.User.Identity.Name;
編輯 Reference.datasvcmap:
產生的實體自動實作 INotifyPropertyChanged/INotifyCollectionChanged,可直接綁定 DataGrid/ComboBox。
public MainPage()
{
InitializeComponent();
_svc = new BlogContext(new Uri("/BlogService.svc", UriKind.Relative));
_blogs = new DataServiceCollection(_svc);
LayoutRoot.DataContext = _blogs;
_blogs.LoadCompleted += Blogs_LoadCompleted;
_blogs.LoadAsync(_svc.Blogs.Expand("Posts")); // 預載貼文
}
void Blogs_LoadCompleted(object s, LoadCompletedEventArgs e)
{
if (e.Error == null && _blogs.Count > 0) cboBlogs.SelectedIndex = 0;
}
private void cboBlogs_SelectionChanged(object s, SelectionChangedEventArgs e)
{
grdPosts.DataContext = cboBlogs.SelectedItem;
// 行計數示範
var q = (from p in _svc.Posts.IncludeTotalCount()
where p.BlogBlogID == ((Blog)cboBlogs.SelectedItem).BlogID
select p) as DataServiceQuery;
((Blog)cboBlogs.SelectedItem).Posts.LoadCompleted += Posts_LoadCompleted;
((Blog)cboBlogs.SelectedItem).Posts.LoadAsync(q);
}
void Posts_LoadCompleted(object s, LoadCompletedEventArgs e)
{
if (e.Error == null)
{
int total = e.QueryOperationResponse.TotalCount;
lblCount.Content = $"Displaying {((Blog)cboBlogs.SelectedItem).Posts.Count} of {total} posts";
}
}
private void btnSave_Click(object s, RoutedEventArgs e)
=> _svc.BeginSaveChanges(SaveChangesOptions.Batch, OnChangesSaved, _svc);
void OnChangesSaved(IAsyncResult ar)
{
var ctx = (BlogContext)ar.AsyncState;
try { ctx.EndSaveChanges(ar); }
catch (Exception ex) { MessageBox.Show(ex.Message); }
}
private void btnMorePosts_Click(object s, RoutedEventArgs e)
{
var blog = (Blog)cboBlogs.SelectedItem;
blog.Posts.LoadCompleted += Posts_LoadCompleted;
blog.Posts.LoadNextPartialSetAsync(); // 取得下一頁
}
// URI 形式
/BlogService.svc/Posts?$select=Title,PostContent
// LINQ 形式 (客戶端)
var q = _svc.Posts.Where(p => p.BlogBlogID == id).Select(p => new { p.Title, p.PostContent });
減少頻寬與記憶體佔用,大數據集查詢極具價值。
| 技術點 | 關鍵 API / 設定 | 商業價值 |
|---|---|---|
| Model-First | Generate Database from Model | 敏捷建模、DBA 協作 |
| POCO | Code Generation Strategy = None | 領域模型純粹性、可測試性 |
| Lazy Loading | virtual 導航屬性 | 按需載入、簡化代碼 |
| OData 服務 | DataService<T> + QueryInterceptor | 跨平台資料開放、安全控制 |
| 雙向綁定 | UseDataServiceCollection=true | Silverlight/WPF MVVM 無縫體驗 |
| 分頁/行計數/投影 | SetEntitySetPageSize / IncludeTotalCount / $select | 大數據效能優化 |