バージョン

DataManager

Visual Basic の場合:

Imports System.Runtime.Serialization
#Region "DataAccess"
Public Class DataManager
#Region "private fields"
    'XML ファイルのルート要素
    Private ReadOnly rootElement As XElement
    'XML ファイルへのパス
    Private ReadOnly dataPath As String
#End Region
#Region "Constructors"
    Public Sub New(ByVal dataPath As String)
        Me.dataPath = dataPath
        'XML ファイルをロードする
        rootElement = XElement.Load(dataPath)
    End Sub
#End Region
#Region "public methods"
#Region "Read"
    Public Function GetCategories() As IEnumerable(Of Category)
        'XML ファイルから Category 要素をすべて取得し、Category オブジェクトを作成します。
        Dim categories = rootElement.Descendants("Category").Select(Of Category)(Function(cElement) CreateCategory(cElement))
        Return categories.ToList()
    End Function
    Public Function GetProducts() As IEnumerable(Of Product)
        'XML ファイルから Product 要素をすべて取得し、Product オブジェクトを作成します。
        Dim products = rootElement.Descendants("Product").Select(Of Product)(Function(pElement) CreateProduct(pElement))
        Return products.ToList()
    End Function
    Public Function GetProductsByCategoryID(ByVal categoryID As Integer) As IEnumerable(Of Product)
        'カテゴリ ID を指定して Product 要素を取得し、Product オブジェクトを作成します。
        Dim products = rootElement.Descendants("Product").Where(Function(pElement) CInt(pElement.Element("CategoryID")) = categoryID).Select(Of Product)(Function(pElement) CreateProduct(pElement))
        Return products.ToList()
    End Function
    Public Function GetCategoriesAndProducts() As IEnumerable(Of Category)
        'Category および Product 要素を取得し、Category および Product オブジェクトを作成します。
        Dim categories = rootElement.Descendants("Category").Select(Of Category)(Function(cElement) CreateCategoryAndProducts(cElement))
        Return categories.ToList()
    End Function
#End Region
#Region "update"
    Public Function UpdateCategory(ByVal updatedCategory As Category) As System.Nullable(Of Integer)
        '指定した Category オブジェクトに対応する Category 要素への参照を取得します。
        Dim originalCategoryElement = rootElement.Descendants("Category").SingleOrDefault(Function(cElement) CInt(cElement.Element("CategoryID")) = updatedCategory.CategoryID)
        '追加の場合は、新しい categoryID を返します。
        Dim categoryID As System.Nullable(Of Integer) = Nothing
        '元のカテゴリ XElement が見つかった場合は、変更を再生します
        'そうでない場合は、変更を再生する前にそのカテゴリの XElement を新規作成します。
        If originalCategoryElement IsNot Nothing Then
            ReplayCategoryChanges(updatedCategory, originalCategoryElement)
        Else
            Dim categoryElement As XElement = Me.AddCategory()
            categoryID = CInt(categoryElement.Element("CategoryID"))
            ReplayCategoryChanges(updatedCategory, categoryElement)
        End If
        'ファイルに保存します
        Me.rootElement.Save(dataPath)
        Return categoryID
    End Function
    '実装は UpdateCategory と同じです
    Public Function UpdateProduct(ByVal updatedProduct As Product) As System.Nullable(Of Integer)
        Dim originalProduct = rootElement.Descendants("Product").SingleOrDefault(Function(pElement) CInt(pElement.Element("ProductID")) = updatedProduct.ProductID)
        Dim productID As System.Nullable(Of Integer) = Nothing
        If originalProduct IsNot Nothing Then
            ReplayProductChanges(updatedProduct, originalProduct)
        Else
            Dim productElement As XElement = Me.AddProduct()
            productID = CInt(productElement.Element("ProductID"))
            ReplayProductChanges(updatedProduct, productElement)
        End If
        Me.rootElement.Save(dataPath)
        Return productID
    End Function
#End Region
#Region "delete"
    Public Sub DeleteCategory(ByVal deletedCategory As Category, ByVal deleteProductsInCategory As Boolean)
        '指定した Category オブジェクトに対応する Category 要素を削除します。
        rootElement.Descendants("Category").Single(Function(cElement) CInt(cElement.Element("CategoryID")) = deletedCategory.CategoryID).Remove()
        If deleteProductsInCategory Then
            '指定した Category オブジェクトに含まれる Product 要素をすべて削除します。
            rootElement.Descendants("Product").Where(Function(pElement) CInt(pElement.Element("CategoryID")) = deletedCategory.CategoryID).Remove()
        End If
        'XML ファイルに対する変更を保持します。
        Me.rootElement.Save(dataPath)
    End Sub
    Public Sub DeleteProduct(ByVal deletedProduct As Product)
        '指定した Product オブジェクトに対応する  Product 要素を削除します。
        rootElement.Descendants("Product").Single(Function(pElement) CInt(pElement.Element("ProductID")) = deletedProduct.ProductID).Remove()
        Me.rootElement.Save(dataPath)
    End Sub
#End Region
#End Region
#Region "private methods"
    'Category オブジェクトを作成し、XElement のデータに基づいてそのプロパティを設定します
    Private Function CreateCategory(ByVal categoryElement As XElement) As Category
        Return New Category With {.CategoryID = CInt(categoryElement.Element("CategoryID")), .CategoryName = categoryElement.Element("CategoryName").Value, .Description = categoryElement.Element("Description").Value, .Products = New List(Of Product)()}
    End Function
    'Product オブジェクトを作成し、XElement のデータに基づいてそのプロパティを設定します
    Private Function CreateProduct(ByVal productElement As XElement) As Product
        Return New Product With {.ProductID = CInt(productElement.Element("ProductID")), .CategoryID = CInt(productElement.Element("CategoryID")), .ProductName = productElement.Element("ProductName").Value, .QuantityPerUnit = productElement.Element("QuantityPerUnit").Value, .UnitPrice = CDec(productElement.Element("UnitPrice")), .UnitsInStock = CInt(productElement.Element("UnitsInStock")), .UnitsOnOrder = CInt(productElement.Element("UnitsOnOrder")), .ReorderLevel = CInt(productElement.Element("ReorderLevel")), .Discontinued = CBool(productElement.Element("Discontinued"))}
    End Function
    'Category オブジェクトと、そのカテゴリに含まれる Product オブジェクトを作成します。
    Private Function CreateCategoryAndProducts(ByVal categoryElement As XElement) As Category
        Dim category As Category = CreateCategory(categoryElement)
        category.Products = rootElement.Descendants("Product").Where(Function(product) CInt(categoryElement.Element("CategoryID")) = CInt(Product.Element("CategoryID"))).Select(Of Product)(Function(product) CreateProduct(product)).ToList()
        Return category
    End Function
    'Category 要素を新規作成して Categories ノードに追加します。
    Private Function AddCategory() As XElement
        'Categories XElement を取得します
        Dim categoriesElement = rootElement.Descendants("Categories").Single()
        'このカテゴリの XElement を新規作成します
        Dim categoryElement As New XElement("Category", New XElement("CategoryID"), New XElement("CategoryName"), New XElement("Description"))
        'CategoryID の最大値に基づいて CategoryID をインクリメントします。
        Dim categoryIDIncrementCounter As Integer = Me.GetMaxCategoryID()
        categoryElement.SetElementValue("CategoryID", categoryIDIncrementCounter + 1)
        'カテゴリ XElement を Categories XElement の子として追加します
        categoriesElement.Add(categoryElement)
        Return categoryElement
    End Function
    '実装は AddCategory メソッドと同じです。
    Private Function AddProduct() As XElement
        Dim productsElement = rootElement.Descendants("Products").Single()
        'Product の XElement を新規作成します。
        Dim productElement As New XElement("Product", New XElement("ProductID"), New XElement("ProductName"), New XElement("CategoryID"), New XElement("QuantityPerUnit"), New XElement("UnitPrice"), _
        New XElement("UnitsInStock"), New XElement("UnitsOnOrder"), New XElement("ReorderLevel"), New XElement("Discontinued"))
        'ProductID の最大値に基づいて ProductID をインクリメントします。
        Dim productIDIncrementCounter As Integer = Me.GetMaxProductID()
        productElement.SetElementValue("ProductID", productIDIncrementCounter + 1)
        'XElement を Products ノードに追加します。
        productsElement.Add(productElement)
        Return productElement
    End Function
    'XML ファイルから CategoryID の最大値を取得するヘルパー メソッド
    Private Function GetMaxCategoryID() As Integer
        Return rootElement.Descendants("Category").Max(Function(categoryElement) CInt(categoryElement.Element("CategoryID")))
    End Function
    'XML ファイルから ProductID の最大値を取得するヘルパー メソッド
    Private Function GetMaxProductID() As Integer
        Return rootElement.Descendants("Product").Max(Function(productElement) CType(productElement.Element("ProductID"), Integer))
    End Function
    '更新した Category オブジェクトに基づいてカテゴリ XElement 値を更新します。
    Private Sub ReplayCategoryChanges(ByVal updatedCategory As Category, ByVal originalCategoryElement As XElement)
        originalCategoryElement.SetElementValue("CategoryName", If(updatedCategory.CategoryName Is Nothing, String.Empty, updatedCategory.CategoryName))
        originalCategoryElement.SetElementValue("Description", If(updatedCategory.Description Is Nothing, String.Empty, updatedCategory.Description))
    End Sub
    '更新した Category オブジェクトに基づいて製品 XElement 値を更新します。
    Private Sub ReplayProductChanges(ByVal updatedProduct As Product, ByVal originalProductElement As XElement)
        originalProductElement.SetElementValue("CategoryID", updatedProduct.CategoryID)
        originalProductElement.SetElementValue("Discontinued", updatedProduct.Discontinued)
        originalProductElement.SetElementValue("ProductName", If(updatedProduct.ProductName Is Nothing, String.Empty, updatedProduct.ProductName))
        originalProductElement.SetElementValue("QuantityPerUnit", If(updatedProduct.QuantityPerUnit Is Nothing, String.Empty, updatedProduct.QuantityPerUnit))
        originalProductElement.SetElementValue("ReorderLevel", updatedProduct.ReorderLevel)
        originalProductElement.SetElementValue("UnitPrice", updatedProduct.UnitPrice)
        originalProductElement.SetElementValue("UnitsInStock", updatedProduct.UnitsInStock)
        originalProductElement.SetElementValue("UnitsOnOrder", updatedProduct.UnitsOnOrder)
    End Sub
#End Region
End Class
#End Region
#Region "Category class"
<DataContract()> _
Public Class Category
    Private _CategoryID As Integer
    <DataMember()> _
    Public Property CategoryID() As Integer
        Get
            Return _CategoryID
        End Get
        Set(ByVal value As Integer)
            _CategoryID = value
        End Set
    End Property
    Private _CategoryName As String
    <DataMember()> _
    Public Property CategoryName() As String
        Get
            Return _CategoryName
        End Get
        Set(ByVal value As String)
            _CategoryName = value
        End Set
    End Property
    Private _Description As String
    <DataMember()> _
    Public Property Description() As String
        Get
            Return _Description
        End Get
        Set(ByVal value As String)
            _Description = value
        End Set
    End Property
    Private _Products As IEnumerable(Of Product)
    <DataMember()> _
    Public Property Products() As IEnumerable(Of Product)
        Get
            Return _Products
        End Get
        Set(ByVal value As IEnumerable(Of Product))
            _Products = value
        End Set
    End Property
End Class
#End Region
#Region "Product class"
<DataContract()> _
Public Class Product
    Private _ProductID As Integer
    <DataMember()> _
    Public Property ProductID() As Integer
        Get
            Return _ProductID
        End Get
        Set(ByVal value As Integer)
            _ProductID = value
        End Set
    End Property
    Private _ProductName As String
    <DataMember()> _
    Public Property ProductName() As String
        Get
            Return _ProductName
        End Get
        Set(ByVal value As String)
            _ProductName = value
        End Set
    End Property
    Private _CategoryID As Integer
    <DataMember()> _
    Public Property CategoryID() As Integer
        Get
            Return _CategoryID
        End Get
        Set(ByVal value As Integer)
            _CategoryID = value
        End Set
    End Property
    Private _QuantityPerUnit As String
    <DataMember()> _
    Public Property QuantityPerUnit() As String
        Get
            Return _QuantityPerUnit
        End Get
        Set(ByVal value As String)
            _QuantityPerUnit = value
        End Set
    End Property
    Private _UnitPrice As Decimal
    <DataMember()> _
    Public Property UnitPrice() As Decimal
        Get
            Return _UnitPrice
        End Get
        Set(ByVal value As Decimal)
            _UnitPrice = value
        End Set
    End Property
    Private _UnitsInStock As Integer
    <DataMember()> _
    Public Property UnitsInStock() As Integer
        Get
            Return _UnitsInStock
        End Get
        Set(ByVal value As Integer)
            _UnitsInStock = value
        End Set
    End Property
    Private _UnitsOnOrder As Integer
    <DataMember()> _
    Public Property UnitsOnOrder() As Integer
        Get
            Return _UnitsOnOrder
        End Get
        Set(ByVal value As Integer)
            _UnitsOnOrder = value
        End Set
    End Property
    Private _ReorderLevel As Integer
    <DataMember()> _
    Public Property ReorderLevel() As Integer
        Get
            Return _ReorderLevel
        End Get
        Set(ByVal value As Integer)
            _ReorderLevel = value
        End Set
    End Property
    Private _Discontinued As Boolean
    <DataMember()> _
    Public Property Discontinued() As Boolean
        Get
            Return _Discontinued
        End Get
        Set(ByVal value As Boolean)
            _Discontinued = value
        End Set
    End Property
End Class
#End Region

C# の場合:

using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Xml.Linq;
using System.Web;
using System.Collections.ObjectModel;
namespace IGDocumentation
{
    #region DataAccess
    public class DataManager
    {
        #region private fields
        //XML ファイルのルート要素
        private readonly XElement rootElement;
        //XML ファイルへのパス
        private readonly string dataPath;
        #endregion
        #region Constructors
        public DataManager(string dataPath)
        {
            this.dataPath = dataPath;
            //XML ファイルをロードする
            rootElement = XElement.Load(dataPath);
        }
        #endregion
        #region public methods
        #region Read
        /*
         * クライアント側ではプロキシによって ObservableCollection<T> が自動的に作成されるため、
         * こうしたメソッドでコレクション I を返すかどうかを考える必要はありません。
         */
        public IEnumerable<Category> GetCategories()
        {
            //XML ファイルから Category 要素をすべて取得し、Category オブジェクトを作成します。
            var categories = rootElement.Descendants("Category").Select<XElement, Category>(cElement => CreateCategory(cElement));
            return categories.ToList<Category>();
        }
        public IEnumerable<Product> GetProducts()
        {
            //XML ファイルから Product 要素をすべて取得し、Product オブジェクトを作成します。
            var products = rootElement.Descendants("Product").Select<XElement, Product>(pElement => CreateProduct(pElement));
            return products.ToList<Product>();
        }
        public IEnumerable<Product> GetProductsByCategoryID(int categoryID)
        {
            //カテゴリ ID を指定して Product 要素を取得し、Product オブジェクトを作成します。
            var products = rootElement.Descendants("Product").Where<XElement>(pElement => (int)pElement.Element("CategoryID") == categoryID).Select<XElement, Product>(pElement => CreateProduct(pElement));
            return products.ToList<Product>();
        }
        public IEnumerable<Category> GetCategoriesAndProducts()
        {
            //Category および Product 要素を取得し、Category および Product オブジェクトを作成します。
            var categories = rootElement.Descendants("Category").Select<XElement, Category>(cElement => CreateCategoryAndProducts(cElement));
            return categories.ToList<Category>();
        }
        #endregion
        #region create/update
        public int? UpdateCategory(Category updatedCategory)
        {
            //指定した Category オブジェクトに対応する Category 要素への参照を取得します。
            var originalCategoryElement = rootElement.Descendants("Category").SingleOrDefault<XElement>(cElement => (int)cElement.Element("CategoryID") == updatedCategory.CategoryID);
            //追加の場合は、新しい categoryID を返します。
            int? categoryID = null;
            //元のカテゴリ XElement が見つかった場合は、変更を再生します
            //そうでない場合は、変更を再生する前にそのカテゴリの XElement を新規作成します。
            if (originalCategoryElement != null)
            {
                ReplayCategoryChanges(updatedCategory, originalCategoryElement);
            }
            else
            {
                XElement categoryElement = this.AddCategory();
                categoryID = (int)categoryElement.Element("CategoryID");
                ReplayCategoryChanges(updatedCategory, categoryElement);
            }
            //ファイルに保存します
            this.rootElement.Save(dataPath);
            return categoryID;
        }
        //実装は UpdateCategory と同じです
        public int? UpdateProduct(Product updatedProduct)
        {
            var originalProduct = rootElement.Descendants("Product").SingleOrDefault<XElement>(pElement => (int)pElement.Element("ProductID") == updatedProduct.ProductID);
            int? productID = null;
            if (originalProduct != null)
            {
                ReplayProductChanges(updatedProduct, originalProduct);
            }
            else
            {
                XElement productElement = this.AddProduct();
                productID = (int)productElement.Element("ProductID");
                ReplayProductChanges(updatedProduct, productElement);
            }
            this.rootElement.Save(dataPath);
            return productID;
        }
        #endregion
        #region delete
        public void DeleteCategory(Category deletedCategory, bool deleteProductsInCategory)
        {
            //指定した Category オブジェクトに対応する Category 要素を削除します。
            rootElement.Descendants("Category").Single<XElement>(cElement => (int)cElement.Element("CategoryID") == deletedCategory.CategoryID).Remove();
            if (deleteProductsInCategory)
            {
                //指定した Category オブジェクトに含まれる Product 要素をすべて削除します。
                rootElement.Descendants("Product").Where<XElement>(pElement => (int)pElement.Element("CategoryID") == deletedCategory.CategoryID).Remove<XElement>();
            }
            //XML ファイルに対する変更を保持します。
            this.rootElement.Save(dataPath);
        }
        public void DeleteProduct(Product deletedProduct)
        {
            //指定した Product オブジェクトに対応する  Product 要素を削除します。
            rootElement.Descendants("Product").Single<XElement>(pElement => (int)pElement.Element("ProductID") == deletedProduct.ProductID).Remove();
            this.rootElement.Save(dataPath);
        }
        #endregion
        #endregion
        #region private methods
        //Category オブジェクトを作成し、XElement のデータに基づいてそのプロパティを設定します
        private Category CreateCategory(XElement categoryElement)
        {
            return new Category
            {
                CategoryID = (int)categoryElement.Element("CategoryID"),
                CategoryName = categoryElement.Element("CategoryName").Value,
                Description = categoryElement.Element("Description").Value,
                Products = new List<Product>()
            };
        }
        //Product オブジェクトを作成し、XElement のデータに基づいてそのプロパティを設定します
        private Product CreateProduct(XElement productElement)
        {
            return new Product
            {
                ProductID = (int)productElement.Element("ProductID"),
                CategoryID = (int)productElement.Element("CategoryID"),
                ProductName = productElement.Element("ProductName").Value,
                QuantityPerUnit = productElement.Element("QuantityPerUnit").Value,
                UnitPrice = (decimal)productElement.Element("UnitPrice"),
                UnitsInStock = (int)productElement.Element("UnitsInStock"),
                UnitsOnOrder = (int)productElement.Element("UnitsOnOrder"),
                ReorderLevel = (int)productElement.Element("ReorderLevel"),
                Discontinued = (bool)productElement.Element("Discontinued")
            };
        }
        private Category CreateCategoryAndProducts(XElement categoryElement)
        {
            Category category = CreateCategory(categoryElement);
            category.Products = rootElement.Descendants("Product").Where<XElement>(product => (int)categoryElement.Element("CategoryID") == (int)product.Element("CategoryID")).Select<XElement, Product>(product => CreateProduct(product)).ToList<Product>();
            return category;
        }
        //Category 要素を新規作成して Categories ノードに追加します。
        private XElement AddCategory()
        {
            //Categories XElement を取得します
            var categoriesElement = rootElement.Descendants("Categories").Single<XElement>();
            //このカテゴリの XElement を新規作成します
            XElement categoryElement = new XElement("Category",
                new XElement("CategoryID"),
                new XElement("CategoryName"),
                new XElement("Description"));
            //CategoryID の最大値に基づいて CategoryID をインクリメントします。
            int categoryIDIncrementCounter = this.GetMaxCategoryID();
            categoryElement.SetElementValue("CategoryID", categoryIDIncrementCounter + 1);
            //カテゴリ XElement を Categories XElement の子として追加します
            categoriesElement.Add(categoryElement);
            return categoryElement;
        }
        //実装は AddCategory メソッドと同じです。
        private XElement AddProduct()
        {
            var productsElement = rootElement.Descendants("Products").Single<XElement>();
            //Product の XElement を新規作成します。
            XElement productElement = new XElement("Product",
                new XElement("ProductID"),
                new XElement("ProductName"),
                new XElement("CategoryID"),
                new XElement("QuantityPerUnit"),
                new XElement("UnitPrice"),
                new XElement("UnitsInStock"),
                new XElement("UnitsOnOrder"),
                new XElement("ReorderLevel"),
                new XElement("Discontinued"));
            //ProductID の最大値に基づいて ProductID をインクリメントします。
            int productIDIncrementCounter = this.GetMaxProductID();
            productElement.SetElementValue("ProductID", productIDIncrementCounter + 1);
            //XElement を Products ノードに追加します。
            productsElement.Add(productElement);
            return productElement;
        }
        //XML ファイルから CategoryID の最大値を取得するヘルパー メソッド
        private int GetMaxCategoryID()
        {
            return rootElement.Descendants("Category").Max<XElement>(categoryElement => (int)categoryElement.Element("CategoryID"));
        }
        //XML ファイルから ProductID の最大値を取得するヘルパー メソッド
        private int GetMaxProductID()
        {
            return rootElement.Descendants("Product").Max<XElement>(productElement => (int)productElement.Element("ProductID"));
        }
        //更新した Category オブジェクトに基づいてカテゴリ XElement 値を更新します。
        private void ReplayCategoryChanges(Category updatedCategory, XElement originalCategoryElement)
        {
            originalCategoryElement.SetElementValue("CategoryName", updatedCategory.CategoryName ?? string.Empty);
            originalCategoryElement.SetElementValue("Description", updatedCategory.Description ?? string.Empty);
        }
        //更新した Category オブジェクトに基づいて製品 XElement 値を更新します。
        private void ReplayProductChanges(Product updatedProduct, XElement originalProductElement)
        {
            originalProductElement.SetElementValue("CategoryID", updatedProduct.CategoryID);
            originalProductElement.SetElementValue("Discontinued", updatedProduct.Discontinued);
            originalProductElement.SetElementValue("ProductName", updatedProduct.ProductName ?? string.Empty);
            originalProductElement.SetElementValue("QuantityPerUnit", updatedProduct.QuantityPerUnit ?? string.Empty);
            originalProductElement.SetElementValue("ReorderLevel", updatedProduct.ReorderLevel);
            originalProductElement.SetElementValue("UnitPrice", updatedProduct.UnitPrice);
            originalProductElement.SetElementValue("UnitsInStock", updatedProduct.UnitsInStock);
            originalProductElement.SetElementValue("UnitsOnOrder", updatedProduct.UnitsOnOrder);
        }
        #endregion
    }
    #endregion
    /*
     * クライアント側ではプロキシによって INotifyPropertyChanged インターフェイスが自動的に作成されるため、INotifyPropertyChanged インターフェイスを実装する必要はありません。
     * WCF 3.5 SP1 を使用する場合には、[DataContract] 属性と [DataMember] 属性を追加する必要もありません。
     * WCF 3.5 SP1 では、既定のコンストラクターでクラスを作成するだけです。読み取り/書き込みプロパティは自動的にシリアル化されます。
     */
    #region Category class
    [DataContract]
    public class Category
    {
        [DataMember]
        public int CategoryID { get; set; }
        [DataMember]
        public string CategoryName { get; set; }
        [DataMember]
        public string Description { get; set; }
        [DataMember]
        public IEnumerable<Product> Products { get; set; }
    }
    #endregion
    #region Product class
    [DataContract]
    public class Product
    {
        [DataMember]
        public int ProductID { get; set; }
        [DataMember]
        public string ProductName { get; set; }
        [DataMember]
        public int CategoryID { get; set; }
        [DataMember]
        public string QuantityPerUnit { get; set; }
        [DataMember]
        public decimal UnitPrice { get; set; }
        [DataMember]
        public int UnitsInStock { get; set; }
        [DataMember]
        public int UnitsOnOrder { get; set; }
        [DataMember]
        public int ReorderLevel { get; set; }
        [DataMember]
        public bool Discontinued { get; set; }
    }
    #endregion
}