WPF/XBAP 与 ADO.NET Entity Framework 的双向数据绑定






4.60/5 (11投票s)
一篇关于在 XBAP/WPF 环境中使用 ADO.NET EF 实现数据绑定的文章

引言
在这篇文章中,我想展示一些在 WPF/XBAP 应用程序中实现双向数据绑定的基本技术。
我无意阐述“庞大”的架构原则,例如将应用程序分为数据逻辑、业务逻辑和UI 逻辑(正如您作为专业开发者应该了解的那样……),但我希望使本文中的内容尽可能简单,并专注于简单的 XBAP/WPF 应用程序可能需要的“数据绑定”基本原理。
在本文中,我将演示一个用于 NorthWind 数据库“Category
”表的简单管理程序的创建。这个小型 XBAP/WPF 程序旨在可视化类别,并允许用户添加/删除或修改类别对象。最后,我添加了一些排序和过滤功能。
背景
读者应具备一些关于 WinForms 数据绑定、连接 SQL Server 数据库的背景知识,以及对 XAML 的初步了解。
Using the Code
1. 模型

首先,我们有“Model
”类,在本例中是 ADO.NET Entity Framework 实体数据模型,仅包含示例所需的 Categories
类。
为了添加一些自定义行为,我创建了一个“Categories
”部分类的重写。
public partial class Categories
{
private bool _isNew;
public bool IsNew
{
get { return _isNew; }
set { _isNew = value; }
}
protected override void OnPropertyChanged(string property)
{
base.OnPropertyChanged(property);
}
private Categories()
{
}
public static Categories Create()
{
Categories c = new Categories();
Bitmap emptyBitMap = WpfBrowserApplication1.Properties.Resources.Empty;
MemoryStream ms = new MemoryStream();
emptyBitMap.Save(ms, ImageFormat.Bmp);
byte[] buffer = ms.ToArray();
c.CategoryName = "[Enter Categoryname]";
c.IsNew = true;
c.Picture = buffer;
return c;
}
}
除了让程序员使用标准的 new object()
方法外,预见一个自定义的对象创建方法是一个好习惯。为此,我将基类构造函数设为private
,并提供了一个 Create()
方法来添加新类别。从代码中可以看到,我添加了一些自定义行为,例如为每个新创建的类别创建一个空的 image
。我还添加了 _isNew
属性,当我们将新添加的对象持久化到数据库时,持久化代码需要此属性。
2. 用户界面存储属性
private static NorthwindEntities nwEntities = new NorthwindEntities();
private List<categories> _categoryList;
private List<categories> _deletedCategoryList;
private CollectionView _view;
private SortDescription _sortByCategoryName;
private SortDescription _sortByDescription;
在我们启动页面 (Page1.xaml) 中,我们需要初始化数据访问对象,即对 NorthWindEntities
的静态引用,然后我们需要一些 Categories
类型的列表来跟踪已加载和在会话期间删除的 Categories
对象,最后我们需要一个 CollectionView
,它提供了一个已排序和/或已过滤的绑定数据视图。
3. 数据加载和绑定
private void Page_Loaded(object sender, RoutedEventArgs e)
{
// Load our Category entities and put them in a locale EntityList
this._categoryList = nwEntities.Categories.ToList();
// Set the DataContext of the Page to our Items List
this.DataContext = _categoryList;
// Create a View to Navigate
this._view = (CollectionView)CollectionViewSource.GetDefaultView(_categoryList);
// Create some sortdescriptions for our view
this._sortByCategoryName = new SortDescription
("CategoryName", ListSortDirection.Ascending);
this._sortByDescription = new SortDescription
("Description", ListSortDirection.Ascending);
// Used to Refresh the current record position display
this._view.CurrentChanged += new EventHandler(_view_CurrentChanged);
this.SetCurrentItemPointer();
}
首先,我们将把类别加载到 _categoryList
集合中。接下来,我们将页面的 DataContext
设置为我们的类别集合,并创建一个数据的视图。这个视图指向我们集合的默认视图,这样就可以导航、排序和过滤我们的集合。我们还创建了一些排序属性,可用于创建数据的排序视图。最后,如果我们的集合发生任何变化,例如用户正在导航、过滤或排序,我们希望 UI 得到更新。
4. 导航
private void btnFirst_Click(object sender, RoutedEventArgs e)
{
this._view.MoveCurrentToFirst();
}
private void btnPrev_Click(object sender, RoutedEventArgs e)
{
if (this._view.CurrentPosition > 0)
this._view.MoveCurrentToPrevious();
}
private void btnNext_Click(object sender, RoutedEventArgs e)
{
if (this._view.CurrentPosition < this._view.Count - 1)
this._view.MoveCurrentToNext();
}
private void btnLast_Click(object sender, RoutedEventArgs e)
{
this._view.MoveCurrentToLast();
}
void _view_CurrentChanged(object sender, EventArgs e)
{
this.SetCurrentItemPointer();
}
private void SetCurrentItemPointer()
{
string currentPosition = Convert.ToString(_view.CurrentPosition + 1);
this.textBlockCurrentItem.Text = currentPosition;
this.textBlockTotalItems.Text = _view.Count.ToString();
}
导航数据,即从一个类别移动到另一个类别……是我们应用程序中的常见行为。我已经为 First、Prev、Next 和 Last 按钮编写了代码,使它们指向列表中的第一个、上一个、下一个或最后一个类别。还要注意,当我们创建的视图上的 CurrentChanged
处理程序将在用户“点击”任何一个上一个按钮时触发。此通知将更新 UI 中的记录指针显示。
5. 排序和过滤
private void btnSortByCategoryName_Click(object sender, RoutedEventArgs e)
{
this._view.SortDescriptions.Clear();
this._view.SortDescriptions.Add(this._sortByCategoryName);
this._view.MoveCurrentToFirst();
}
private void btnSortByDescription_Click(object sender, RoutedEventArgs e)
{
this._view.SortDescriptions.Clear();
this._view.SortDescriptions.Add(this._sortByDescription);
this._view.MoveCurrentToFirst();
}
private void btnResetSort_Click(object sender, RoutedEventArgs e)
{
this._view.SortDescriptions.Clear();
this._view.MoveCurrentToFirst();
}
private void _searchBar_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox source = e.OriginalSource as TextBox;
if (source == null)
return;
switch (source.Name)
{
case "_searchText":
// Filter CategoryName
//this._view.Filter = new Predicate[object](Contains);
FilterCategory predicate = new FilterCategory(source.Text);
this._view.Filter = predicate.Match;
if (this._view.Count > 0)
this._view.MoveCurrentToFirst();
break;
}
}
public class FilterCategory
{
private string _filter;
public FilterCategory(string p_filter)
{
_filter = p_filter;
}
public Predicate[object] Match
{
get { return Contains; }
}
public bool Contains(object p_category)
{
Categories category = p_category as Categories;
if (_filter == string.Empty)
{
return (category.CategoryName != string.Empty);
}
return (category.CategoryName.Contains(_filter));
}
}
通过开始时添加的排序属性,对 CollectionView
进行排序很容易实现。我还添加了一个简单的过滤器,即 CategoryName
。过滤器代码更具挑战性。在这种情况下,我创建了一个过滤器类,它根据用户提供的搜索文本来过滤对象列表,然后将记录指针设置为列表中的第一个对象并刷新 UI。
6. 添加新类别
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
// Create new Category and add to the Current List
this._categoryList.Add(Categories.Create());
this._view.MoveCurrentToLast();
}
如你所见,创建新的 Category
并将其添加到集合列表中是一个非常简单的过程!
7. 删除类别
private void btnDelete_Click(object sender, RoutedEventArgs e)
{
// Remove the Current Item from the Current List
if (_view.CurrentPosition > -1)
{
Categories c = (Categories)_view.CurrentItem;
if (c != null)
{
// Add Deleted Item to the Deleted List
if (this._deletedCategoryList == null)
{
this._deletedCategoryList = new List[Categories]();
}
this._deletedCategoryList.Add(c);
int currentPosition = (this._view.CurrentPosition - 1);
this._categoryList.Remove(c);
this._view.Refresh();
if (currentPosition > -1)
{
this._view.MoveCurrentToPosition(currentPosition);
}
}
}
}
首先,我们获取视图的当前项,并将其添加到已删除项列表中(持久化需要 -> 稍后查看)。接下来,我们从当前列表中删除该对象,并在存在时定位到下一条记录。
8. 持久化我们的新、修改和删除的对象
private void btnSave_Click(object sender, RoutedEventArgs e)
{
// Add Categories First
IEnumerable[Categories] newCategories =
this._categoryList.Where(cat => cat.IsNew == true);
foreach (Categories newCategory in newCategories)
{
nwEntities.AddToCategories(newCategory);
newCategory.IsNew = false;
}
// Delete Categories
if (this._deletedCategoryList != null)
{
foreach (Categories deleteCategory in this._deletedCategoryList)
{
nwEntities.DeleteObject(deleteCategory);
}
}
// Save the Changes
nwEntities.SaveChanges();
// Clear the Deleted Category List
if(this._deletedCategoryList != null)
this._deletedCategoryList.Clear();
}
在 EF 中,数据持久化以保存数据……是一个非常简单的过程!所有 SQL 相关命令、适配器、连接参数等都对用户隐藏,因此可以编写非常易读的代码来持久化我们的数据。我认为上面代码中的注释是不言自明的!
9. 结语
感谢阅读!希望您喜欢。当然……再次强调,在专业环境中,尤其是在大型项目中,您应该将代码分层,将数据访问抽象到数据层,通过业务层进行 UI 绑定和验证,等等……但出于教育目的,我只是让这个应用程序尽可能简单!
历史
- 2009年3月13日:初始版本