チュートリアル: データ ソース拡張機能の作成

このチュートリアルでは、LightSwitch の "データ ソース" 拡張機能を作成する方法について説明します。 データ ソース拡張機能では、他のデータ ソースと共に使用するために、カスタム ドメイン サービスのアダプター クラスが使用されます。 これにより、LightSwitch でネイティブでサポートされるデータ ソースにないデータを含め、ほぼすべてのデータにアクセスできます。

機能拡張の場合、LightSwitch では、カスタム DomainService クラスへの呼び出しが、一種のメモリ内データ アダプターとしてサポートされます。 LightSwitch はインスタンスをデータ サービス実装から直接呼び出して、クエリ操作と送信操作を実行します。 カスタム ドメイン サービスはパブリック サービスとしては公開されないため、[EnableClientAccess] 属性は適用しないでください。 この機構を使用すると、エンティティ型を公開し、特定のクエリ、挿入、更新、削除の各メソッドが実装される、DomainService クラスを作成できます。 LightSwitch は、公開されたエンティティ型に基づいて LightSwitch エンティティ モデルを推測します。また、"既定の" クエリの存在に基づいてエンティティ セットを推測します。

データ ソース拡張機能を作成するには、次のタスクを実行します。

  • データ ソース拡張機能プロジェクトを作成する。

  • データ ソース クラスを実装する。

  • 製品クラスおよび製品カテゴリ クラスを作成する。

  • データ ソース拡張機能をテストする。

必須コンポーネント

  • Visual Studio 2013 Professional

  • Visual Studio 2013 SDK

  • LightSwitch Extensibility Toolkit for Visual Studio 2013

データ ソース拡張機能プロジェクトを作成する

最初にプロジェクトを作成して、LightSwitch データ ソース テンプレートを追加します。

拡張機能プロジェクトを作成するには

  1. Visual Studio のメニュー バーで、[ファイル][新規作成][プロジェクト] の順にクリックします。

  2. [新しいプロジェクト] ダイアログ ボックスで、[Visual Basic] ノードまたは [Visual C#] ノードを展開します。次に、[LightSwitch] ノードを展開し、[機能拡張] ノード、[LightSwitch 拡張ライブラリ] テンプレートの順に選択します。

  3. [名前] フィールドに、「DataSourceExtension」と入力します。 この拡張ライブラリ名は、LightSwitch アプリケーション デザイナー[拡張機能] タブに表示されます。

  4. [OK] をクリックして、拡張機能に必要な 7 つのプロジェクトが含まれるソリューションを作成します。

拡張機能の種類を選択するには

  1. ソリューション エクスプローラーで、[DataSourceExtension.Lspkg] プロジェクトを選択します。

  2. メニュー バーで [プロジェクト][新しい項目の追加] の順に選択します。

  3. [新しい項目の追加] ダイアログ ボックスで、[LightSwitch データ ソース] を選択します。

  4. [名前] フィールドに、「XMLDataSource」と入力します。 この拡張機能の名前は、LightSwitch のデータ ソースのアタッチ ウィザードに表示されます。

  5. [OK] を選択します。 XMLDataSource.vb ファイルまたは XMLDataSource.cs ファイルが、ソリューションの DataSourceExtension.Server プロジェクトに追加されます。

データ ソース クラスを実装する

データ ソース拡張機能のロジックのほとんどが、XMLDataSource.vb ファイルまたは XMLDataSource.cs ファイルに作成された XMLDataSource クラスに含まれます。 必要な参照と、Imports ステートメントまたは using ステートメントは、既にプロジェクトに追加されています。 XML データ ソースについては、さらに名前空間の参照をいくつか追加する必要があります。

名前空間の参照を追加するには

  • ソリューション エクスプローラーで、[DataSourceExtension.Server] プロジェクトの [XMLDataSource.vb] ファイルまたは [XMLDataSource.cs] ファイルを開きます。 次の Imports ステートメントまたは using ステートメントをファイルの先頭に追加します。

    Imports System.Xml.Linq
    Imports System.Configuration
    Imports System.Web.Configuration
    
    using System.Xml.Linq;
    using System.Configuration;
    using System.Web.Configuration;
    

次に、Description 属性を追加して、LightSwitch のデータ ソースのアタッチ ウィザードの説明を指定します。

説明属性を追加するには

  • 次のコードを、// TODO: Create methods containing your application logic. コメント行の直後に追加します。

    'The description attribute will be displayed in the LightSwitch data attach wizard.
        <Description("Please provide the path to the XML file.")> _
    
    //The description attribute will be displayed in the LightSwitch data attach wizard.
        [Description("Please provide the path to the XML file.")]
    

次に、名前空間を指定します。

名前空間を指定するには

  • 既存の名前空間の宣言を、次のコードに置き換えます。

    Namespace DataSourceExtension.DataSources
    
    namespace DataSourceExtension.DataSources
    

次に、コードを追加して、ファイル パスと接続文字列を設定します。

初期化メソッドを実装するには

  • 次のコードでは、必要な場合に、web.config ファイルからファイル名と接続文字列を取得し、ファイルを確認して、XML 要素を追加します。 XMLDataSource クラスに次のコードを追加します。

    Private _db As XElement
            Private _filePath As String
    
            Public Overrides Sub Initialize(context As DomainServiceContext)
                MyBase.Initialize(context)
    
                'Get the file path from config.
                If WebConfigurationManager.ConnectionStrings(GetType(XMLDataSource).FullName) IsNot Nothing Then
                    _filePath = WebConfigurationManager.ConnectionStrings(GetType(XMLDataSource).FullName).ConnectionString
                End If
    
                If [String].IsNullOrWhiteSpace(_filePath) Then
                    Throw New Exception("The filepath must be provided in the web.config in the connection strings section under " & GetType(XMLDataSource).FullName)
                Else
                    If Not System.IO.File.Exists(_filePath) Then
                        Dim x As New XElement("DataRoot")
                        x.Save(_filePath)
                    End If
    
                    _db = XElement.Load(_filePath)
    
                    'Verify the file.
                    If _db.Name <> "DataRoot" Then
                        Throw New Exception("Corrupt file.")
                    End If
    
                    'Add a product categories node if one does not exist.
                    If _db.Element("ProductCategories") Is Nothing Then
                        _db.Add(New XElement("ProductCategories"))
                    End If
    
                    'Add a products node if one does not exist.
                    If _db.Element("Products") Is Nothing Then
                        _db.Add(New XElement("Products"))
                    End If
                End If
            End Sub
    
    private XElement _db;
            private string _filePath;
    
            public override void Initialize(DomainServiceContext context)
            {
                base.Initialize(context);
    
                //Get the file path from config.
                if (WebConfigurationManager.ConnectionStrings[typeof(XMLDataSource).FullName] != null)
                    _filePath = WebConfigurationManager.ConnectionStrings[typeof(XMLDataSource).FullName].ConnectionString;
    
                if (String.IsNullOrWhiteSpace(_filePath))
                {
                    throw new Exception("The filepath must be provided in the web.config in the connection strings section under " + typeof(XMLDataSource).FullName);
                }
                else
                {
                    if (!System.IO.File.Exists(_filePath))
                    {
                        XElement x = new XElement("DataRoot");
                        x.Save(_filePath);
                    }
    
                    _db = XElement.Load(_filePath);
    
                    //Verify the file.
                    if (_db.Name != "DataRoot")
                        throw new Exception("Corrupt file.");
    
                    //Add a product categories node if one does not exist.
                    if (_db.Element("ProductCategories") == null)
                        _db.Add(new XElement("ProductCategories"));
    
                    //Add a products node if one does not exist.
                    if (_db.Element("Products") == null)
                        _db.Add(new XElement("Products"));
                }
            }
    

次に、Submit メソッドを実装して、ファイルへの変更を保存します。

送信メソッドを実装するには

  • XMLDataSource クラスに次のコードを追加します。

    Public Overrides Function Submit(changeSet As ChangeSet) As Boolean
                Dim c As New ChangeSet(changeSet.ChangeSetEntries.OrderBy(Function(entry) entry.Entity, New ProductEntitiesComparer()))
    
                Dim baseResult As [Boolean] = MyBase.Submit(changeSet)
                If baseResult Then
                    _db.Save(_filePath)
                    Return True
                Else
                    Return False
                End If
            End Function
    
    public override bool Submit(ChangeSet changeSet)
            {
                ChangeSet c = new ChangeSet(changeSet.ChangeSetEntries.OrderBy(entry => entry.Entity, new ProductEntitiesComparer()));
    
                Boolean baseResult = base.Submit(changeSet);
                if (baseResult)
                {
                    _db.Save(_filePath);
                    return true;
                }
                else
                    return false;
            }
    

    注意

    この時点では、ProductEntitiesComparer クラスがまだ実装されていないため、エラーが表示されます。このクラスは後で実装するために、このエラーは無視してかまいません。

次に、2 つの既定のクエリ メソッドを実装します。製品用のメソッドと製品カテゴリ用のメソッドです。

クエリ メソッドを実装するには

  • 新しい領域を XMLDataSource クラスに追加します。

    #Region "Queries"
    
    #End Region
    
    #region Queries
    
            #endregion
    
  • 次のコードを Queries 領域に追加します。

    Protected Overrides Function Count(Of T)(query As IQueryable(Of T)) As Integer
                Return query.Count()
            End Function
    
            <Query(IsDefault:=True)> _
            Public Function GetProducts() As IQueryable(Of Product)
                Dim products As New List(Of Product)()
                For Each pElem As XElement In _db.Descendants("Product")
                    products.Add(GetProduct(pElem))
                Next
                Return products.AsQueryable()
            End Function
    
            <Query(IsDefault:=True)> _
            Public Function GetProductCategories() As IQueryable(Of ProductCategory)
                Dim categories As New List(Of ProductCategory)()
                For Each catElem As XElement In _db.Descendants("ProductCategory")
                    categories.Add(GetProductCategory(catElem))
                Next
    
                Return categories.AsQueryable()
            End Function
    
    protected override int Count<T>(IQueryable<T> query)
            {
                return query.Count();
            }
    
            [Query(IsDefault = true)]
            public IQueryable<Product> GetProducts()
            {
                List<Product> products = new List<Product>();
                foreach (XElement pElem in _db.Descendants("Product"))
                {
                    products.Add(GetProduct(pElem));
                }
                return products.AsQueryable();
            }
    
            [Query(IsDefault = true)]
            public IQueryable<ProductCategory> GetProductCategories()
            {
                List<ProductCategory> categories = new List<ProductCategory>();
                foreach (XElement catElem in _db.Descendants("ProductCategory"))
                {
                    categories.Add(GetProductCategory(catElem));
                }
    
                return categories.AsQueryable();
            }
    

    注意

    この時点で、さらにエラーが表示されます。このエラーは、チュートリアルのすべてのコードが追加された時点で解決されるため、無視してかまいません。

次に、2 種類のカスタム クエリを実装します。 このクエリは、デザイン時に LightSwitch のデータ ソース ノードに表示されます。

カスタム クエリを実装するには

  1. CustomQueries 領域を XMLDataSource クラスに追加します。

    #Region "CustomQueries"
    
    #End Region
    
    #region CustomQueries
    
    #endregion
    
  2. 次のコードを CustomQueries 領域に追加します。

    'All query parameters need to be nullable. 
            Public Function GetProductsByCategory(categoryID As Nullable(Of Guid)) As IQueryable(Of Product)
                Dim products As New List(Of Product)()
    
                'Return no products if categoryID is as null.
                If categoryID.HasValue Then
                    For Each pElem As XElement In _db.Descendants("Product")
                        Dim p As Product = GetProduct(pElem)
                        If p.CategoryID = categoryID.Value Then
                            products.Add(p)
                        End If
                    Next
                End If
                Return products.AsQueryable()
            End Function
    
            'Query that returns a single item.
            'Mark queries that return as single item as composable = false.
            <Query(IsComposable:=False)> _
            Public Function GetProductByID(productID As Nullable(Of Guid)) As Product
                Dim product As Product = Nothing
    
                'Only return a result if you are passed a value.
                If productID.HasValue Then
                    Dim pElem As XElement = (From p In _db.Descendants("Product") Where p.Element("ProductID").Value = productID.Value.ToString() Select p).FirstOrDefault()
                    If pElem IsNot Nothing Then
                        product = GetProduct(pElem)
                    End If
                End If
                Return product
    
            End Function
    
    //All query parameters need to be nullable. 
            public IQueryable<Product> GetProductsByCategory(Nullable<Guid> categoryID)
            {
                List<Product> products = new List<Product>();
    
                //Return no products if categoryID is as null.
                if (categoryID.HasValue)
                {
                    foreach (XElement pElem in _db.Descendants("Product"))
                    {
                        Product p = GetProduct(pElem);
                        if (p.CategoryID == categoryID.Value)
                        {
                            products.Add(p);
                        }
                    }
                }
                return products.AsQueryable();
            }
    
            //Query that returns a single item.
            //Mark queries that return as single item as composable = false.
            [Query(IsComposable = false)]
            public Product GetProductByID(Nullable<Guid> productID)
            {
                Product product = null;
    
                //Only return a result if you are passed a value.
                if (productID.HasValue)
                {
                    XElement pElem = (from XElement p in _db.Descendants("Product")
                                      where p.Element("ProductID").Value == productID.Value.ToString()
                                      select p).FirstOrDefault();
                    if (pElem != null)
                    {
                        product = GetProduct(pElem);
                    }
                }
                return product;
    
            }
    

次に、製品カテゴリに対して、Update、Delete、Insert の各メソッドを定義します。

更新、削除、および挿入の各メソッドを追加するには

  1. Category Update/Delete/Insert Methods 領域を XMLDataSource クラスに追加します。

    #Region "Category Update/Delete/Insert Methods"
    
    #End Region
    
    #region Category Update/Delete/Insert Methods
    
    #endregion
    
  2. 次のコードを Category Update/Delete/Insert Methods 領域に追加します。

    Public Sub InsertProductCategory(pc As ProductCategory)
                Try
                    pc.CategoryID = Guid.NewGuid()
                    Dim catElem As XElement = GetProductCategoryElem(pc)
    
                    'Update the category ID on any related product entities.
                    For Each e As ChangeSetEntry In ChangeSet.ChangeSetEntries
                        If TypeOf e.Entity Is Product Then
                            If CObj(e.Entity).Category = CObj(pc) Then
                                DirectCast(e.Entity, Product).CategoryID = pc.CategoryID
                            End If
                        End If
                    Next
    
    
                    'Update the xml doc.
                    _db.Element("ProductCategories").Add(catElem)
                Catch ex As Exception
                    Throw New Exception("Error inserting ProductCategory " & Convert.ToString(pc.CategoryName), ex)
                End Try
            End Sub
    
            Public Sub UpdateProductCategory(pc As ProductCategory)
                Try
                    'Get existing item from the XML db.
                    Dim storeCatElem As XElement = (From c In _db.Descendants("ProductCategory") Where c.Element("CategoryID").Value = pc.CategoryID.ToString() Select c).FirstOrDefault()
    
                    If storeCatElem Is Nothing Then
                        'Category does not exist.  Indicate that item has already been deleted.
                        Dim entry As ChangeSetEntry = (From e In ChangeSet.ChangeSetEntries Where e.Entity = CObj(pc) Select e).First()
                        entry.IsDeleteConflict = True
                    Else
                        Dim storeCategory As ProductCategory = GetProductCategory(storeCatElem)
    
                        'Find entry in the changeset to compare original values.
                        Dim entry As ChangeSetEntry = (From e In ChangeSet.ChangeSetEntries Where e.Entity = CObj(pc) Select e).First()
                        'List of conflicting fields.
                        Dim conflictMembers As New List(Of [String])()
    
                        If storeCategory.CategoryName <> DirectCast(entry.OriginalEntity, ProductCategory).CategoryName Then
                            conflictMembers.Add("CategoryName")
                        End If
                        If storeCategory.Description <> DirectCast(entry.OriginalEntity, ProductCategory).Description Then
                            conflictMembers.Add("Description")
                        End If
    
                        'Set conflict members.
                        entry.ConflictMembers = conflictMembers
                        entry.StoreEntity = storeCategory
    
                        If conflictMembers.Count < 1 Then
                            'Update the xml _db.
                            storeCatElem.ReplaceWith(GetProductCategoryElem(pc))
                        End If
                    End If
                Catch ex As Exception
                    Throw New Exception("Error updating Category " & pc.CategoryID.ToString() & ":" & Convert.ToString(pc.CategoryName), ex)
                End Try
            End Sub
    
            Public Sub DeleteProductCategory(pc As ProductCategory)
                Try
                    Dim storeCatElem As XElement = (From c In _db.Descendants("ProductCategory") Where c.Element("CategoryID").Value = pc.CategoryID.ToString() Select c).FirstOrDefault()
    
                    If storeCatElem Is Nothing Then
                        'Category does not exist.  Indicate that item has already been deleted.
                        Dim entry As ChangeSetEntry = (From e In ChangeSet.ChangeSetEntries Where e.Entity = CObj(pc) Select e).First()
                        entry.IsDeleteConflict = True
                    Else
                        storeCatElem.Remove()
                    End If
                Catch ex As Exception
                    Throw New Exception("Error deleting Category " & pc.CategoryID.ToString() & ":" & Convert.ToString(pc.CategoryName), ex)
                End Try
            End Sub
    
    public void InsertProductCategory(ProductCategory pc)
            {
                try
                {
                    pc.CategoryID = Guid.NewGuid();
                    XElement catElem = GetProductCategoryElem(pc);
    
                    //Update the category ID on any related product entities.
                    foreach (ChangeSetEntry e in ChangeSet.ChangeSetEntries)
                    {
                        if (e.Entity is Product)
                        {
                            if (((Product)e.Entity).Category == pc)
                            {
                                ((Product)e.Entity).CategoryID = pc.CategoryID;
                            }
                        }
                    }
    
    
                    //Update the xml doc.
                    _db.Element("ProductCategories").Add(catElem);
                }
                catch (Exception ex)
                {
                    throw new Exception("Error inserting ProductCategory " + pc.CategoryName, ex);
                }
            }
    
            public void UpdateProductCategory(ProductCategory pc)
            {
                try
                {
                    //Get existing item from the XML db.
                    XElement storeCatElem =
                        (from c in _db.Descendants("ProductCategory")
                         where c.Element("CategoryID").Value == pc.CategoryID.ToString()
                         select c).FirstOrDefault();
    
                    if (storeCatElem == null)
                    {
                        //Category does not exist.  Indicate that the item has already been deleted.
                        ChangeSetEntry entry =
                            (from e in ChangeSet.ChangeSetEntries
                             where e.Entity == pc
                             select e).First();
                        entry.IsDeleteConflict = true;
                    }
                    else
                    {
                        ProductCategory storeCategory = GetProductCategory(storeCatElem);
    
                        //Find entry in the changeset to compare original values.
                        ChangeSetEntry entry =
                            (from e in ChangeSet.ChangeSetEntries
                             where e.Entity == pc
                             select e).First();
    
                        //List of conflicting fields.
                        List<String> conflictMembers = new List<String>();
    
                        if (storeCategory.CategoryName != ((ProductCategory)entry.OriginalEntity).CategoryName)
                            conflictMembers.Add("CategoryName");
                        if (storeCategory.Description != ((ProductCategory)entry.OriginalEntity).Description)
                            conflictMembers.Add("Description");
    
                        //Set conflict members>
                        entry.ConflictMembers = conflictMembers;
                        entry.StoreEntity = storeCategory;
    
                        if (conflictMembers.Count < 1)
                        {
                            //Update the xml _db.
                            storeCatElem.ReplaceWith(GetProductCategoryElem(pc));
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw new Exception("Error updating Category " + pc.CategoryID.ToString() + ":" + pc.CategoryName, ex);
                }
            }
    
            public void DeleteProductCategory(ProductCategory pc)
            {
                try
                {
                    XElement storeCatElem =
                        (from c in _db.Descendants("ProductCategory")
                         where c.Element("CategoryID").Value == pc.CategoryID.ToString()
                         select c).FirstOrDefault();
    
                    if (storeCatElem == null)
                    {
                        //Category does not exist.  Indicate that the item has already been deleted.
                        ChangeSetEntry entry =
                            (from e in ChangeSet.ChangeSetEntries
                             where e.Entity == pc
                             select e).First();
                        entry.IsDeleteConflict = true;
                    }
                    else
                    {
                        storeCatElem.Remove();
                    }
                }
                catch (Exception ex)
                {
                    throw new Exception("Error deleting Category " + pc.CategoryID.ToString() + ":" + pc.CategoryName, ex);
                }
            }
    

次に、類似した一連の製品メソッドを実装します。

メソッドを追加するには

  1. Product Update/Delete/Insert Methods 領域を XMLDataSource クラスに追加します。

    #Region "Product Update/Delete/Insert Methods"
    
    #End Region
    
    #region Product Update/Delete/Insert Methods
    
    #endregion
    
  2. 次のコードを Product Update/Delete/Insert Methods 領域に追加します。

    Public Sub InsertProduct(p As Product)
                Try
                    p.ProductID = Guid.NewGuid()
                    Dim productElem As XElement = GetProductElem(p)
                    _db.Element("Products").Add(productElem)
                Catch ex As Exception
                    Throw New Exception("Error inserting Product " & Convert.ToString(p.ProductName), ex)
                End Try
            End Sub
    
            Public Sub UpdateProduct(p As Product)
                Try
                    Dim storeProductElem As XElement = (From c In _db.Descendants("Product") Where c.Element("ProductID").Value = p.ProductID.ToString() Select c).FirstOrDefault()
    
                    If storeProductElem Is Nothing Then
                        'Product does not exist.  Indicate that the item has already been deleted.
                        Dim entry As ChangeSetEntry = (From e In ChangeSet.ChangeSetEntries Where e.Entity = CObj(p) Select e).First()
                        entry.IsDeleteConflict = True
                    Else
                        Dim storeProduct As Product = GetProduct(storeProductElem)
    
                        'Find the entry in the changeset to compare original values.
                        Dim entry As ChangeSetEntry = (From e In ChangeSet.ChangeSetEntries Where e.Entity = CObj(p) Select e).First()
    
                        'List of conflicting fields.
                        Dim conflictMembers As New List(Of [String])()
    
                        If storeProduct.ProductName <> DirectCast(entry.OriginalEntity, Product).ProductName Then
                            conflictMembers.Add("ProductName")
                        End If
                        If storeProduct.CategoryID <> DirectCast(entry.OriginalEntity, Product).CategoryID Then
                            conflictMembers.Add("CategoryID")
                        End If
                        If storeProduct.QuantityPerUnit <> DirectCast(entry.OriginalEntity, Product).QuantityPerUnit Then
                            conflictMembers.Add("QuantityPerUnit")
                        End If
                        If storeProduct.UnitPrice <> DirectCast(entry.OriginalEntity, Product).UnitPrice Then
                            conflictMembers.Add("UnitPrice")
                        End If
                        If storeProduct.UnitsInStock <> DirectCast(entry.OriginalEntity, Product).UnitsInStock Then
                            conflictMembers.Add("UnitsInStock")
                        End If
                        If storeProduct.ReorderLevel <> DirectCast(entry.OriginalEntity, Product).ReorderLevel Then
                            conflictMembers.Add("ReorderLevel")
                        End If
                        If storeProduct.Discontinued <> DirectCast(entry.OriginalEntity, Product).Discontinued Then
                            conflictMembers.Add("Discontinued")
                        End If
    
                        'Set conflict members.
                        entry.ConflictMembers = conflictMembers
                        entry.StoreEntity = storeProduct
    
                        If conflictMembers.Count < 1 Then
                            'Update the xml _db.
                            storeProductElem.ReplaceWith(GetProductElem(p))
                        End If
                    End If
                Catch ex As Exception
                    Throw New Exception("Error updating Product " & p.ProductID.ToString() & ":" & Convert.ToString(p.ProductName), ex)
                End Try
            End Sub
    
            Public Sub DeleteProduct(p As Product)
                Try
                    Dim storeProductElem As XElement = (From c In _db.Descendants("Product") Where c.Element("ProductID").Value = p.ProductID.ToString() Select c).FirstOrDefault()
    
                    If storeProductElem Is Nothing Then
                        'Product does not exist.  Indicate that the item has already been deleted.
                        Dim entry As ChangeSetEntry = (From e In ChangeSet.ChangeSetEntries Where e.Entity = CObj(p) Select e).First()
                        entry.IsDeleteConflict = True
                    Else
                        'Remove it.
                        storeProductElem.Remove()
                    End If
                Catch ex As Exception
                    Throw New Exception("Error deleting Product " & p.ProductID.ToString() & ":" & Convert.ToString(p.ProductName), ex)
                End Try
            End Sub
    
    public void InsertProduct(Product p)
            {
                try
                {
                    p.ProductID = Guid.NewGuid();
                    XElement productElem = GetProductElem(p);
                    _db.Element("Products").Add(productElem);
                }
                catch (Exception ex)
                {
                    throw new Exception("Error inserting Product " + p.ProductName, ex);
                }
            }
    
            public void UpdateProduct(Product p)
            {
                try
                {
                    XElement storeProductElem =
                        (from c in _db.Descendants("Product")
                         where c.Element("ProductID").Value == p.ProductID.ToString()
                         select c).FirstOrDefault();
    
                    if (storeProductElem == null)
                    {
                        //Product does not exist.  Indicate that the item has already been deleted.
                        ChangeSetEntry entry =
                            (from e in ChangeSet.ChangeSetEntries
                             where e.Entity == p
                             select e).First();
                        entry.IsDeleteConflict = true;
                    }
                    else
                    {
                        Product storeProduct = GetProduct(storeProductElem);
    
                        //Find the entry in the changeset to compare original values.
                        ChangeSetEntry entry =
                            (from e in ChangeSet.ChangeSetEntries
                             where e.Entity == p
                             select e).First();
    
                        //List of conflicting fields.
                        List<String> conflictMembers = new List<String>();
    
                        if (storeProduct.ProductName != ((Product)entry.OriginalEntity).ProductName)
                            conflictMembers.Add("ProductName");
                        if (storeProduct.CategoryID != ((Product)entry.OriginalEntity).CategoryID)
                            conflictMembers.Add("CategoryID");
                        if (storeProduct.QuantityPerUnit != ((Product)entry.OriginalEntity).QuantityPerUnit)
                            conflictMembers.Add("QuantityPerUnit");
                        if (storeProduct.UnitPrice != ((Product)entry.OriginalEntity).UnitPrice)
                            conflictMembers.Add("UnitPrice");
                        if (storeProduct.UnitsInStock != ((Product)entry.OriginalEntity).UnitsInStock)
                            conflictMembers.Add("UnitsInStock");
                        if (storeProduct.ReorderLevel != ((Product)entry.OriginalEntity).ReorderLevel)
                            conflictMembers.Add("ReorderLevel");
                        if (storeProduct.Discontinued != ((Product)entry.OriginalEntity).Discontinued)
                            conflictMembers.Add("Discontinued");
    
                        //Set conflict members.
                        entry.ConflictMembers = conflictMembers;
                        entry.StoreEntity = storeProduct;
    
                        if (conflictMembers.Count < 1)
                        {
                            //Update the xml _db.
                            storeProductElem.ReplaceWith(GetProductElem(p));
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw new Exception("Error updating Product " + p.ProductID.ToString() + ":" + p.ProductName, ex);
                }
            }
    
            public void DeleteProduct(Product p)
            {
                try
                {
                    XElement storeProductElem =
                        (from c in _db.Descendants("Product")
                         where c.Element("ProductID").Value == p.ProductID.ToString()
                         select c).FirstOrDefault();
    
                    if (storeProductElem == null)
                    {
                        //The product does not exist.  Indicate that the item has already been deleted.
                        ChangeSetEntry entry =
                            (from e in ChangeSet.ChangeSetEntries
                             where e.Entity == p
                             select e).First();
                        entry.IsDeleteConflict = true;
                    }
                    else
                    {
                        //Remove it.
                        storeProductElem.Remove();
                    }
                }
                catch (Exception ex)
                {
                    throw new Exception("Error deleting Product " + p.ProductID.ToString() + ":" + p.ProductName, ex);
                }
            }
    

次に、ヘルパー関数を追加します。

ヘルパー関数を実装するには

  • 新しい領域を XMLDataSource クラスに追加します。

    #Region “HelperFunctions”
    
    #End Region
    
    #region HelperFunctions
    
    #endregion
    
  • 次のコードを Queries 領域に追加します。

    Private Function GetProductElem(p As Product) As XElement
                Dim productElem As New XElement("Product", New XElement("ProductID", New Object() {New XAttribute("DataType", "Guid"), p.ProductID}), New XElement("ProductName", New Object() {New XAttribute("DataType", "String"), p.ProductName}), New XElement("CategoryID", New Object() {New XAttribute("DataType", "Guid"), p.CategoryID}), New XElement("QuantityPerUnit", New Object() {New XAttribute("DataType", "String"), p.QuantityPerUnit}), New XElement("UnitPrice", New Object() {New XAttribute("DataType", "Decimal"), p.UnitPrice}), _
                 New XElement("UnitsInStock", New Object() {New XAttribute("DataType", "Int32"), p.UnitsInStock}), New XElement("ReorderLevel", New Object() {New XAttribute("DataType", "Int32?"), p.ReorderLevel}), New XElement("Discontinued", New Object() {New XAttribute("DataType", "Boolean"), p.Discontinued}))
                Return productElem
            End Function
    
            Private Function GetProduct(pElem As XElement) As Product
                Dim p As New Product()
    
                p.ProductID = New Guid(pElem.Element("ProductID").Value)
                p.ProductName = pElem.Element("ProductName").Value
                p.CategoryID = New Guid(pElem.Element("CategoryID").Value)
                p.QuantityPerUnit = pElem.Element("QuantityPerUnit").Value
                p.UnitPrice = Decimal.Parse(pElem.Element("UnitPrice").Value)
                p.UnitsInStock = Int32.Parse(pElem.Element("UnitsInStock").Value)
                If Not [String].IsNullOrWhiteSpace(pElem.Element("ReorderLevel").Value) Then
                    p.ReorderLevel = Int32.Parse(pElem.Element("ReorderLevel").Value)
                End If
                p.Discontinued = [Boolean].Parse(pElem.Element("Discontinued").Value)
    
                Return p
            End Function
    
            Private Function GetProductCategory(catElem As XElement) As ProductCategory
                Dim pc As New ProductCategory()
                pc.CategoryID = New Guid(catElem.Element("CategoryID").Value)
                pc.CategoryName = catElem.Element("CategoryName").Value
                pc.Description = catElem.Element("Description").Value
    
                Return pc
            End Function
    
            Private Function GetProductCategoryElem(pc As ProductCategory) As XElement
                Dim catElem As New XElement("ProductCategory", New XElement("CategoryID", New Object() {New XAttribute("DataType", "Guid"), pc.CategoryID}), New XElement("CategoryName", New Object() {New XAttribute("DataType", "String"), pc.CategoryName}), New XElement("Description", New Object() {New XAttribute("DataType", "String"), pc.Description}))
                Return catElem
            End Function
    
    private XElement GetProductElem(Product p)
            {
                XElement productElem = new XElement("Product",
                    new XElement("ProductID", new object[] { new XAttribute("DataType", "Guid"), p.ProductID }),
                    new XElement("ProductName", new object[] { new XAttribute("DataType", "String"), p.ProductName }),
                    new XElement("CategoryID", new object[] { new XAttribute("DataType", "Guid"), p.CategoryID }),
                    new XElement("QuantityPerUnit", new object[] { new XAttribute("DataType", "String"), p.QuantityPerUnit }),
                    new XElement("UnitPrice", new object[] { new XAttribute("DataType", "Decimal"), p.UnitPrice }),
                    new XElement("UnitsInStock", new object[] { new XAttribute("DataType", "Int32"), p.UnitsInStock }),
                    new XElement("ReorderLevel", new object[] { new XAttribute("DataType", "Int32?"), p.ReorderLevel }),
                    new XElement("Discontinued", new object[] { new XAttribute("DataType", "Boolean"), p.Discontinued })
                    );
                return productElem;
            }
    
            private Product GetProduct(XElement pElem)
            {
                Product p = new Product();
    
                p.ProductID = new Guid(pElem.Element("ProductID").Value);
                p.ProductName = pElem.Element("ProductName").Value;
                p.CategoryID = new Guid(pElem.Element("CategoryID").Value);
                p.QuantityPerUnit = pElem.Element("QuantityPerUnit").Value;
                p.UnitPrice = decimal.Parse(pElem.Element("UnitPrice").Value);
                p.UnitsInStock = Int32.Parse(pElem.Element("UnitsInStock").Value);
                if (!String.IsNullOrWhiteSpace(pElem.Element("ReorderLevel").Value))
                {
                    p.ReorderLevel = Int32.Parse(pElem.Element("ReorderLevel").Value);
                }
                p.Discontinued = Boolean.Parse(pElem.Element("Discontinued").Value);
    
                return p;
            }
    
            private ProductCategory GetProductCategory(XElement catElem)
            {
                ProductCategory pc = new ProductCategory();
                pc.CategoryID = new Guid(catElem.Element("CategoryID").Value);
                pc.CategoryName = catElem.Element("CategoryName").Value;
                pc.Description = catElem.Element("Description").Value;
    
                return pc;
            }
    
            private XElement GetProductCategoryElem(ProductCategory pc)
            {
                XElement catElem = new XElement("ProductCategory",
                   new XElement("CategoryID", new object[] { new XAttribute("DataType", "Guid"), pc.CategoryID }),
                   new XElement("CategoryName", new object[] { new XAttribute("DataType", "String"), pc.CategoryName }),
                   new XElement("Description", new object[] { new XAttribute("DataType", "String"), pc.Description })
                   );
                return catElem;
            }
    

次に、Comparer クラスを XMLDataSource.vb ファイルまたは XMLDataSource.cs ファイルに追加します。

比較子クラスを追加するには

  • 次のコードでは、必要な場合に、web.config ファイルからファイル名と接続文字列を取得し、ファイルを確認して、XML 要素を追加します。 DataSourceExtension.DataSources 名前空間の内部で、XMLDataSource クラスの後にコードを追加します。

    Public Class ProductEntitiesComparer
            Inherits Comparer(Of Object)
            Public Overrides Function Compare(x As Object, y As Object) As Integer
                If TypeOf x Is Product AndAlso TypeOf y Is ProductCategory Then
                    Return 1
                ElseIf TypeOf x Is ProductCategory AndAlso TypeOf y Is Product Then
                    Return -1
                Else
                    Return 0
                End If
            End Function
        End Class
    
    public class ProductEntitiesComparer : Comparer<object>
        {
            public override int Compare(object x, object y)
            {
                if (x is Product && y is ProductCategory)
                    return 1;
                else if (x is ProductCategory && y is Product)
                    return -1;
                else
                    return 0;
            }
        }
    

XMLDataSource クラスはこれで完了です。 このクラスのコードにはサービス接続関連のコードだけでなく、2 つのクエリが含まれます。ID によって製品を取得するクエリと、カテゴリによって製品を取得するクエリです。 これらのクエリは、LightSwitch アプリケーションでも使用できます。

次の手順では、Product データと ProductCategory データを格納する、2 つのクラスを作成します。

製品クラスおよび製品カテゴリ クラスを作成する

XMLDataSource クラスのほかに、Product エンティティと ProductCategory エンティティを表すための 2 つのクラスがさらに必要です。 これらの 2 つのクラスは、DataSourceExtension.Server プロジェクトに追加されます。

Product クラスを追加するには

  1. ソリューション エクスプローラーで、[DataSourceExtension.Server] プロジェクトを選択します。

  2. [プロジェクト] メニューの [クラスの追加] を選択します。

  3. [新しい項目の追加] ダイアログ ボックスで [名前] フィールドを選択し、「Product」と入力して、[追加] を選択します。

  4. Product ファイルで、既存の内容を次のコードに置き換えます。

    Imports System.Collections.Generic
    Imports System.Linq
    Imports System.Text
    Imports System.ComponentModel
    Imports System.ComponentModel.DataAnnotations
    
    
    Namespace DataSourceExtension
        Public Class Product
            <Key()> _
            <[ReadOnly](True)> _
            <Display(Name:="Product ID")> _
            <ScaffoldColumn(False)> _
            Public Property ProductID() As Guid
                Get
                    Return m_ProductID
                End Get
                Set(value As Guid)
                    m_ProductID = value
                End Set
            End Property
            Private m_ProductID As Guid
    
            <Required()> _
            <Display(Name:="Product Name")> _
            Public Property ProductName() As String
                Get
                    Return m_ProductName
                End Get
                Set(value As String)
                    m_ProductName = value
                End Set
            End Property
            Private m_ProductName As String
    
            <Required()> _
            <Display(Name:="Category ID")> _
            Public Property CategoryID() As Guid
                Get
                    Return m_CategoryID
                End Get
                Set(value As Guid)
                    m_CategoryID = value
                End Set
            End Property
            Private m_CategoryID As Guid
    
            <Display(Name:="Quantity Per Unit")> _
            Public Property QuantityPerUnit() As String
                Get
                    Return m_QuantityPerUnit
                End Get
                Set(value As String)
                    m_QuantityPerUnit = value
                End Set
            End Property
            Private m_QuantityPerUnit As String
    
            <Range(0, Double.MaxValue, ErrorMessage:="The specified price must be greater than zero.")> _
            <Display(Name:="Unit Price")> _
            Public Property UnitPrice() As [Decimal]
                Get
                    Return m_UnitPrice
                End Get
                Set(value As [Decimal])
                    m_UnitPrice = value
                End Set
            End Property
            Private m_UnitPrice As [Decimal]
    
            <Display(Name:="Units In Stock")> _
            <Range(0, Int32.MaxValue, ErrorMessage:="Cannot have a negative quantity of products.")> _
            Public Property UnitsInStock() As Int32
                Get
                    Return m_UnitsInStock
                End Get
                Set(value As Int32)
                    m_UnitsInStock = value
                End Set
            End Property
            Private m_UnitsInStock As Int32
    
            <Display(Name:="Reorder Level")> _
            Public Property ReorderLevel() As Nullable(Of Int32)
                Get
                    Return m_ReorderLevel
                End Get
                Set(value As Nullable(Of Int32))
                    m_ReorderLevel = value
                End Set
            End Property
            Private m_ReorderLevel As Nullable(Of Int32)
    
            <Display(Name:="Discontinued")> _
            Public Property Discontinued() As [Boolean]
                Get
                    Return m_Discontinued
                End Get
                Set(value As [Boolean])
                    m_Discontinued = value
                End Set
            End Property
            Private m_Discontinued As [Boolean]
    
            <Association("Product_Category", "CategoryID", "CategoryID", IsForeignKey:=True)> _
            <Display(Name:="Category")> _
            Public Property Category() As ProductCategory
                Get
                    Return m_Category
                End Get
                Set(value As ProductCategory)
                    m_Category = value
                End Set
            End Property
            Private m_Category As ProductCategory
        End Class
    End Namespace
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    
    
    namespace DataSourceExtension
    {
        public class Product
        {
            [Key()]
            [ReadOnly(true)]
            [Display(Name = "Product ID")]
            [ScaffoldColumn(false)]
            public Guid ProductID { get; set; }
    
            [Required()]
            [Display(Name = "Product Name")]
            public string ProductName { get; set; }
    
            [Required()]
            [Display(Name = "Category ID")]
            public Guid CategoryID { get; set; }
    
            [Display(Name = "Quantity Per Unit")]
            public string QuantityPerUnit { get; set; }
    
            [Range(0, double.MaxValue, ErrorMessage = "The specified price must be greater than zero.")]
            [Display(Name = "Unit Price")]
            public Decimal UnitPrice { get; set; }
    
            [Display(Name = "Units In Stock")]
            [Range(0, Int32.MaxValue, ErrorMessage = "Cannot have a negative quantity of products.")]
            public Int32 UnitsInStock { get; set; }
    
            [Display(Name = "Reorder Level")]
            public Nullable<Int32> ReorderLevel { get; set; }
    
            [Display(Name = "Discontinued")]
            public Boolean Discontinued { get; set; }
    
            [Association("Product_Category", "CategoryID", "CategoryID", IsForeignKey = true)]
            [Display(Name = "Category")]
            public ProductCategory Category { get; set; }
        }
    }
    

ProductCategory クラスを追加するには

  1. ソリューション エクスプローラーで、[DataSourceExtension.Server] プロジェクトを選択します。

  2. [プロジェクト] メニューの [クラスの追加] を選択します。

  3. [新しい項目の追加] ダイアログ ボックスで [名前] フィールドを選択し、「ProductCategory」と入力して、[追加] を選択します。

  4. ProductCategory ファイルで、既存の内容を次のコードに置き換えます。

    Imports System.Collections.Generic
    Imports System.Linq
    Imports System.Text
    Imports System.ComponentModel
    Imports System.ComponentModel.DataAnnotations
    
    Namespace DataSourceExtension
        Public Class ProductCategory
            <[ReadOnly](True)> _
            <Key()> _
            <Display(Name:="Category ID")> _
            <ScaffoldColumn(False)> _
            Public Property CategoryID() As Guid
                Get
                    Return m_CategoryID
                End Get
                Set(value As Guid)
                    m_CategoryID = Value
                End Set
            End Property
            Private m_CategoryID As Guid
    
            <Required()> _
            <Display(Name:="Category Name")> _
            Public Property CategoryName() As String
                Get
                    Return m_CategoryName
                End Get
                Set(value As String)
                    m_CategoryName = Value
                End Set
            End Property
            Private m_CategoryName As String
    
            <Display(Name:="Description")> _
            Public Property Description() As [String]
                Get
                    Return m_Description
                End Get
                Set(value As [String])
                    m_Description = Value
                End Set
            End Property
            Private m_Description As [String]
    
            <Display(Name:="Products")> _
            <Association("Product_Category", "CategoryID", "CategoryID")> _
            Public Property Products() As ICollection(Of Product)
                Get
                    Return m_Products
                End Get
                Set(value As ICollection(Of Product))
                    m_Products = Value
                End Set
            End Property
            Private m_Products As ICollection(Of Product)
        End Class
    End Namespace
    
     using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    
    namespace DataSourceExtension
    {
        public class ProductCategory
        {
            [ReadOnly(true)]
            [Key()]
            [Display(Name = "Category ID")]
            [ScaffoldColumn(false)]
            public Guid CategoryID { get; set; }
    
            [Required()]
            [Display(Name = "Category Name")]
            public string CategoryName { get; set; }
    
            [Display(Name = "Description")]
            public String Description { get; set; }
    
            [Display(Name = "Products")]
            [Association("Product_Category", "CategoryID", "CategoryID")]
            public ICollection<Product> Products { get; set; }
        }
    }
    

データ ソース拡張機能のコードはこれで完了です。 この時点で、Visual Studio の実験用インスタンスでデータソース拡張機能をテストできます。

データ ソース拡張機能をテストする

Visual Studio の実験用インスタンスでデータ ソース拡張機能をテストできます。 他の LightSwitch 機能拡張をまだテストしていない場合は、まず、実験用インスタンスを有効にする必要があります。

実験用インスタンスを有効にするには

  1. ソリューション エクスプローラーで、[BusinessTypeExtension.Vsix] プロジェクトを選択します。

  2. メニュー バーで、[プロジェクト][BusinessTypeExtension.Vsix Properties] の順に選択します。

  3. [デバッグ] タブの [開始動作] で、[外部プログラムを起動する] を選択します。

  4. Visual Studio の実行可能ファイル devenv.exe のパスを入力します。

    既定のパスは、32 ビット システムの場合は C:\Program Files\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe、64 ビット システムの場合は C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe です。

  5. [コマンド ライン引数] フィールドに、「/rootsuffix Exp」と入力します。

    注意

    この設定は、すべての後続 LightSwitch 機能拡張プロジェクトで既定で使用されます。

データ ソース拡張機能をテストするには

  1. メニュー バーで、[デバッグ][デバッグ開始] の順に選択します。 Visual Studio の実験用のインスタンスが開きます。

  2. 実験用インスタンスのメニュー バーで、[ファイル][新規作成][プロジェクト] の順に選択します。

  3. [新しいプロジェクト] ダイアログ ボックスで、[Visual Basic] ノードまたは [Visual C#] ノードを展開し、[LightSwitch] ノード、[LightSwitch デスクトップ アプリケーション] テンプレートの順に選択します。

  4. [名前] フィールドに「DataSourceTest」と入力し、[OK] を選択して、テスト プロジェクトを作成します。

  5. メニュー バーで [プロジェクト][DataSourceTest Properties] の順に選択します。

  6. プロジェクト デザイナーの [拡張機能] タブで、[DataSourceExtension] チェック ボックスをオンにします。

  7. メニュー バーで、[プロジェクト][データ ソースの追加] の順に選択します。

  8. データ ソースのアタッチ ウィザードで、[WCF RIA サービス][次へ] の順に選択します。

  9. [DataSourceExtension.DataSources.XMLDataSource][次へ] の順に選択します。

  10. [エンティティ] ノードを展開し、[Product][ProductCategory] の両方を選択します。

  11. [接続文字列] フィールドに「C:\Temp\Products.xml」と入力し、[完了] を選択します。

    ソリューション エクスプローラーで、データ ソースが [データ ソース] ノードに追加されます。 ProductCategory テーブルと Products テーブルの両方が表示されていること、および GetProductByID クエリと GetProductsByCategory クエリが [製品] に表示されていることに注意してください。

  12. テーブルとクエリを使用する画面が含まれるアプリケーションを作成し、そのアプリケーションを実行して動作を確認します。

    データ ソースが、他のデータ ソースと同じように機能することを確認します。 これにより、データを追加、削除、および更新できます。

次の手順

データ ソースのチュートリアルはこれで完了です。この時点で、すべての LightSwitch プロジェクトで再利用でき、十分に機能するデータ ソース拡張機能を利用できます。 ここで説明したデータ ソースは 1 つの例に過ぎません。このため、別のデータの種類にアクセスするデータ ソースの作成が必要になる場合がありますが、 適用する基本的な手順と原則は同じです。

拡張機能を配布する場合は、さらに実行する手順がいくつかあります。 プロジェクト デザイナー拡張機能マネージャーに表示される情報の正確性を確保するために、VSIX パッケージのプロパティを更新できます。 詳細については、「方法: VSIX パッケージのプロパティを設定する」を参照してください。 また、拡張機能を配布する場合に考慮する必要がある点もいくつかあります。 詳細については、「方法: LightSwitch 拡張機能を配布する」を参照してください。

参照

処理手順

方法: VSIX パッケージのプロパティを設定する

方法: LightSwitch 拡張機能を配布する

概念

Visual Studio 2013 の LightSwitch Extensibility Toolkit