星期四, 11月 01, 2018

[EF] WinForm DataBinding

參考這篇 MSDN 文章 - 資料繫結與 WinForm 的練習筆記

[EF] WinForm DataBinding-5

IListSource 實作集合
  • 這個類別會啟用雙向資料繫結,以及排序。
  • 類別衍生自 ObservableCollection<T>並新增明確的 IListSource 實作。
  • IListSource getlist() 方法實作傳回 IBindingList 實作,它與 ObservableCollection 保持同步。
  • 產生 ToBindingList IBindingList 實作支援排序。
  • EntityFramework 組件中定義 ToBindingList 擴充方法。
// ObservableCollection<T> 所在 namespace
using System.Collections.ObjectModel;
// IListSource 所在 namespace
using System.ComponentModel;
// IList 所在 namespace
using System.Collections;
// EntityFramework 組件中定義 ToBindingList 擴充方法
using System.Data.Entity;

namespace WinFormswithEFSample
{
    public class ObservableListSource<T> : ObservableCollection<T>, IListSource
        where T : class
    {
        private IBindingList _bindingList;

        public bool ContainsListCollection
        {
            get { return false; }
        }

        public IList GetList()
        {
            return _bindingList ?? (_bindingList = this.ToBindingList());
        }
    }
}

定義 Model
namespace WinFormswithEFSample.Models
{
    using System.ComponentModel.DataAnnotations;

    public partial class Products
    {
        [Key]
        public int ProductId { get; set; }

        public string Name { get; set; }

        public int CategoryId { get; set; }

        public virtual Categories Categories { get; set; }
    }
}
namespace WinFormswithEFSample.Models
{
    using System.ComponentModel.DataAnnotations;

    public partial class Categories
    {
        private readonly ObservableListSource<Products> _products =
                    new ObservableListSource<Products>();

        [Key]
        public int CategoryId { get; set; }

        public string Name { get; set; }

        public virtual ObservableListSource<Products> Products { get { return _products; } }
    }
}
namespace WinFormswithEFSample.Models
{
    using System.Data.Entity;

    public partial class DataBindingContext : DbContext
    {
        public DataBindingContext()
            : base("name=DataBindingContext")
        {
        }

        public virtual DbSet<Categories> Categories { get; set; }
        public virtual DbSet<Products> Products { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
        }
    }
}
CREATE TABLE [dbo].[Categories] (
    [CategoryId] [int] NOT NULL IDENTITY,
    [Name] [nvarchar](max),
    CONSTRAINT [PK_dbo.Categories] PRIMARY KEY ([CategoryId])
)

CREATE TABLE [dbo].[Products] (
    [ProductId] [int] NOT NULL IDENTITY,
    [Name] [nvarchar](max),
    [CategoryId] [int] NOT NULL,
    CONSTRAINT [PK_dbo.Products] PRIMARY KEY ([ProductId])
)

CREATE INDEX [IX_CategoryId] ON [dbo].[Products]([CategoryId])

-- 有啟用 FK 的 DELETE CASCADE 功能
ALTER TABLE [dbo].[Products] ADD CONSTRAINT [FK_dbo.Products_dbo.Categories_CategoryId] FOREIGN KEY ([CategoryId]) REFERENCES [dbo].[Categories] ([CategoryId]) ON DELETE CASCADE

物件繫結至控制項

在資料來源視窗內新增

[EF] WinForm DataBinding-1

利用 [物件] 來新增

[EF] WinForm DataBinding-2

選擇 Categories Class 為資料來源,萬一 WinFormswithEFSample 沒有出現,請先 build project 後就會看見啦

[EF] WinForm DataBinding-3

資料來源設定完成

[EF] WinForm DataBinding-4

直接拖曳進 WinForm 就會產生 DataGridView 和對應的 BindingSource

[EF] WinForm DataBinding-7

WinForm Code
using WinFormswithEFSample.Models;
using System.Data.Entity;

namespace WinFormswithEFSample
{
    public partial class Form1 : Form
    {
        private DataBindingContext _context;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            _context = new DataBindingContext();

            // Call the Load method to get the data for the given DbSet
            // from the database.
            // The data is materialized as entities. The entities are managed by
            // the DbContext instance.

            // 利用 Load() 載進 Categories 內資料
            // Load() 和 ToList() 差異
            // ToList() 會產生 List<T>
            // Load 不會產生 List<T>,只會把資料載進 Local 內而已
            _context.Categories.Load();

            // Bind the categoryBindingSource.DataSource to
            // all the Unchanged, Modified and Added Category objects that
            // are currently tracked by the DbContext.
            // Note that we need to call ToBindingList() on the
            // ObservableCollection<TEntity> returned by
            // the DbSet.Local property to get the BindingList<T>
            // in order to facilitate two-way binding in WinForms.
            this.categoriesBindingSource.DataSource =
                _context.Categories.Local.ToBindingList();
        }

        private void categoriesBindingNavigatorSaveItem_Click(object sender, EventArgs e)
        {
            this.Validate();

            // Currently, the Entity Framework doesn’t mark the entities
            // that are removed from a navigation property (in our example the Products)
            // as deleted in the context.
            // The following code uses LINQ to Objects against the Local collection
            // to find all products and marks any that do not have
            // a Category reference as deleted.
            // The ToList call is required because otherwise
            // the collection will be modified
            // by the Remove call while it is being enumerated.
            // In most other situations you can do LINQ to Objects directly
            // against the Local property without using ToList first.

            // 導覽屬性並不會把 EntityState 設為 deleted,因此在 SaveChange 前必須手動移除
            foreach (var product in _context.Products.Local.ToList())
            {
                if (product.Categories == null)
                {
                    _context.Products.Remove(product);
                }
            }

            // Save the changes to the database.
            this._context.SaveChanges();

            // Refresh the controls to show the values         
            // that were generated by the database.
            this.categoriesDataGridView.Refresh();
            this.productsDataGridView.Refresh();
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            base.OnClosing(e);
            _context.Dispose();
        }
    }
}
執行結果
  • Master 會自動顯示 Detail 資料
  • 執行 SaveChange 時 Category、Product 資料都會一併存進 DB 內

[EF] WinForm DataBinding-6

沒有留言:

張貼留言