Exemplarische Vorgehensweise: Erstellen einer Datenquellen-Erweiterung
Diese exemplarische Vorgehensweise veranschaulicht, wie eine Datenquellenerweiterung für LightSwitch erstellt wird. Eine Datenquellenerweiterung verwendet eine benutzerdefinierte Domänendienst-Adapterklasse für die Zusammenarbeit mit anderen Datenquellen. Damit können Sie auf nahezu alle Daten zugreifen, die sich in einer Datenquelle befinden, die nicht systemeigen von LightSwitch unterstützt wird.
Für die Erweiterbarkeit unterstützt LightSwitch den Aufruf einer benutzerdefinierten DomainService-Klasse als eine Art Datenadapter im Arbeitsspeicher. LightSwitch ruft die Instanz direkt von der Datendienstimplementierung auf, um Abfragen und Sendevorgänge auszuführen. Der benutzerdefinierte Domänendienst wird nicht als öffentlicher Dienst verfügbar gemacht, daher sollte das Attribut [EnableClientAccess] nicht angewendet werden. Mithilfe dieses Mechanismus können Sie eine DomainService-Klasse erstellen, die Entitätstypen verfügbar macht und die vorgeschriebenen Abfrage-, Einfüge-, Aktualisierungs- und Löschmethoden implementiert. LightSwitch leitet auf Grundlage der verfügbar gemachten Entitätstypen das LightSwitch-Entitätsmodell und aus dem Vorhandensein einer Standardabfrage eine Entitätenmenge ab.
Das Erstellen einer Datenquellenerweiterung umfasst die folgenden Aufgaben:
Erstellen eines Projekts für die Datenquellenerweiterung
Implementieren der Datenquellenklasse
Erstellen von Produkt- und Produktkategorieklassen
Testen der Datenquellenerweiterung
Vorbereitungsmaßnahmen
Visual Studio 2013 Professional
Visual Studio 2013 SDK
LightSwitch Extensibility Toolkit für Visual Studio 2013
Erstellen eines Projekts für die Datenquellenerweiterung
Der erste Schritt besteht darin, ein Projekt zu erstellen und eine LightSwitch-Datenquellenvorlage hinzuzufügen.
So erstellen Sie ein Erweiterungsprojekt
Wählen Sie in der Visual Studio-Menüleiste Datei, Neu und Projekt aus.
Erweitern Sie im Dialogfeld Neues Projekt den Knoten Visual Basic oder Visual C#, erweitern Sie den Knoten LightSwitch, wählen Sie dann den Knoten Erweiterungen und anschließend die Vorlage LightSwitch-Erweiterungsbibliothek aus.
Geben Sie im Feld Name die Zeichenfolge DataSourceExtension ein. Dieser Name der Erweiterungsbibliothek erscheint auf der Registerkarte Erweiterungen des LightSwitch-Anwendungs-Designers.
Wählen Sie die Schaltfläche OK, um eine Projektmappe zu erstellen, die die sieben Projekte enthält, die für die Erweiterung erforderlich sind.
So wählen Sie einen Erweiterungstyp aus
Wählen Sie im Projektmappen-Explorer das Projekt DataSourceExtension.Lspkg aus.
Wählen Sie in der Menüleiste Projekt, Neues Element hinzufügen aus.
Wählen Sie im Dialogfeld Neues Element hinzufügen die Option LightSwitch-Datenquelle aus.
Geben Sie im Feld Name die Zeichenfolge XMLDataSource ein. Dieser Erweiterungsname wird im Assistenten zum Hinzufügen von Datenquellen von LightSwitch angezeigt.
Klicken Sie auf die Schaltfläche OK. Eine XMLDataSource.vb- oder XMLDataSource.cs-Datei wird dem Projekt DataSourceExtension.Server in der Projektmappe hinzugefügt.
Implementieren der Datenquellenklasse
Der Großteil der Logik für die Datenquellenerweiterung ist in der Klasse XMLDataSource enthalten, die in der XMLDataSource.vb- oder der XMLDataSource.cs-Datei erstellt wird. Die erforderlichen Verweise und Imports- oder using-Anweisungen wurden dem Projekt bereits hinzugefügt. Bei einer XML-Datenquelle sind weitere Namespaceverweise hinzuzufügen.
So fügen Sie Namespaceverweise hinzu
Öffnen Sie im Projektmappen-Explorer die XMLDataSource.vb- oder XMLDataSource.cs-Datei im Projekt DataSourceExtension.Server. Fügen Sie am Anfang der Datei die folgende Imports- oder using-Anweisung hinzu.
Imports System.Xml.Linq Imports System.Configuration Imports System.Web.Configuration
using System.Xml.Linq; using System.Configuration; using System.Web.Configuration;
Fügen Sie dann ein Description-Attribut hinzu, um eine Beschreibung im Assistenten zum Hinzufügen von Datenquellen von LightSwitch bereitzustellen.
So fügen Sie ein Beschreibungsattribut hinzu
Fügen Sie nach der Kommentarzeile // TODO: Create methods containing your application logic. folgenden Code hinzu.
'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.")]
Anschließend geben Sie den Namespace an.
So geben Sie den Namespace an
Ersetzen Sie die vorhandene Namespacedeklaration durch die Folgende.
Namespace DataSourceExtension.DataSources
namespace DataSourceExtension.DataSources
Nun fügen Sie Code hinzu, um den Dateipfad und die Verbindungszeichenfolge festzulegen.
So implementieren Sie die Initialisierungsmethode
Folgender Code ruft den Dateinamen und die Verbindungszeichenfolge aus der Datei "web.config" ab, überprüft die Datei und fügt bei Bedarf die XML-Elemente hinzu. Fügen Sie diesen Code zur XMLDataSource-Klasse hinzu.
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")); } }
Anschließend implementieren Sie die Methode Submit, um Änderungen an der Datei zu speichern.
So implementieren Sie die Sendemethode
Fügen Sie der XMLDataSource-Klasse folgenden Code hinzu.
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; }
Hinweis
Zu diesen Zeitpunkt erhalten Sie eine Fehlermeldung, da die Klasse ProductEntitiesComparer noch nicht implementiert ist.Sie können diesen Fehler ignorieren, die Klasse wird später implementiert.
Als Nächstes implementieren Sie zwei standardmäßige Abfragemethoden in der Klasse, eine für Produkte und eine für Produktkategorien.
So implementieren Sie Abfragemethoden
Fügen Sie der Klasse XMLDataSource eine neue Region hinzu.
#Region "Queries" #End Region
#region Queries #endregion
Fügen Sie der Queries-Region folgenden Code hinzu.
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(); }
Hinweis
Zu diesem Zeitpunkt werden weitere Fehler angezeigt.Sie können die Fehler ignorieren, denn sobald der gesamte Code für die exemplarische Vorgehensweise hinzugefügt wurde, werden die Fehler behoben.
Anschließend implementieren Sie zwei benutzerdefinierte Abfragen. Diese werden zur Entwurfszeit im Knoten Datenquellen von LightSwitch angezeigt.
So implementieren Sie benutzerdefinierte Abfragen
Fügen Sie eine CustomQueries-Region zur XMLDataSource-Klasse hinzu.
#Region "CustomQueries" #End Region
#region CustomQueries #endregion
Fügen Sie der CustomQueries-Region folgenden Code hinzu.
'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; }
Anschließend definieren Sie die Methoden Update, Delete und Insert für Produktkategorien.
So fügen Sie Aktualisierungs-, Lösch- und Einfügemethoden hinzu
Fügen Sie eine Category Update/Delete/Insert Methods-Region zur XMLDataSource-Klasse hinzu.
#Region "Category Update/Delete/Insert Methods" #End Region
#region Category Update/Delete/Insert Methods #endregion
Fügen Sie der Category Update/Delete/Insert Methods-Region folgenden Code hinzu.
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); } }
Nun implementieren Sie einen vergleichbaren Methodensatz für Produkte.
So fügen Sie Methoden hinzu
Fügen Sie eine Product Update/Delete/Insert Methods-Region zur XMLDataSource-Klasse hinzu.
#Region "Product Update/Delete/Insert Methods" #End Region
#region Product Update/Delete/Insert Methods #endregion
Fügen Sie der Product Update/Delete/Insert Methods-Region folgenden Code hinzu.
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); } }
Als Nächstes fügen Sie einige Hilfsfunktionen hinzu.
So implementieren Sie Hilfsfunktionen
Fügen Sie der Klasse XMLDataSource eine neue Region hinzu.
#Region “HelperFunctions” #End Region
#region HelperFunctions #endregion
Fügen Sie der Queries-Region folgenden Code hinzu.
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; }
Fügen Sie nun eine Comparer-Klasse zur XMLDataSource.vb- oder XMLDataSource.cs-Datei hinzu.
So fügen Sie eine Vergleichsklasse hinzu
Folgender Code ruft den Dateinamen und die Verbindungszeichenfolge aus der Datei "web.config" ab, überprüft die Datei und fügt bei Bedarf die XML-Elemente hinzu. Fügen Sie den Code nach der XMLDataSource-Klasse im DataSourceExtension.DataSources-Namespace ein.
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; } }
Mit diesen Schritten ist die Klasse XMLDataSource beendet. Der Code in dieser Klasse verfügt nicht nur über dienstverbindungsbezogenen Code, sondern auch über zwei Abfragen, eine zum Abrufen des Produkts nach ID und die zweite zum Abrufen des Produkts nach Kategorie. Diese Abfragen können ebenfalls in einer LightSwitch-Anwendung verwendet werden.
Der nächste Schritt besteht darin, zwei Klassen zum Speichern der Product- und ProductCategory-Daten zu erstellen.
Erstellen von Produkt- und Produktkategorieklassen
Neben der Klasse XMLDataSource benötigen Sie zwei weitere Klassen zur Abbildung der Product- und ProductCategory-Entitäten. Diese beiden Klassen werden dem Projekt DataSourceExtension.Server hinzugefügt.
So fügen Sie die Produktklasse hinzu
Wählen Sie im Projektmappen-Explorer das Projekt DataSourceExtension.Server aus.
Wählen Sie im Menü Projekt den Eintrag Klasse hinzufügen aus.
Im Dialogfeld Neues Element hinzufügen wählen Sie das Feld Name aus und geben Product ein. Wählen Sie dann die Schaltfläche Hinzufügen aus.
Ersetzen Sie in der Datei Product den vorhandenen Inhalt durch folgenden Code.
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; } } }
So fügen Sie die ProductCategory-Klasse hinzu
Wählen Sie im Projektmappen-Explorer das Projekt DataSourceExtension.Server aus.
Wählen Sie im Menü Projekt den Eintrag Klasse hinzufügen aus.
Im Dialogfeld Neues Element hinzufügen wählen Sie das Feld Name aus und geben ProductCategory ein. Wählen Sie dann die Schaltfläche Hinzufügen aus.
Ersetzen Sie in der Datei ProductCategory den vorhandenen Inhalt durch folgenden Code.
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; } } }
Mit diesen Schritten ist der Code für die Datenquellenerweiterung beendet. Jetzt können Sie einen Test in einer experimentellen Instanz von Visual Studio vornehmen.
Testen der Datenquellenerweiterung
Sie können die Datenquellenerweiterung in einer experimentellen Instanz von Visual Studio testen. Falls Sie nicht bereits ein anderes LightSwitch-Erweiterungsprojekt getestet haben, müssen Sie die experimentelle Instanz zunächst aktivieren.
So aktivieren Sie eine experimentelle Instanz
Wählen Sie im Projektmappen-Explorer das BusinessTypeExtension.Vsix-Projekt aus.
Wählen Sie in der Menüleiste Projekt und dann die Option für Eigenschaften von BusinessTypeExtension.Vsix aus.
Wählen Sie auf der Registerkarte Debuggen unter Startaktion die Option Externes Programm starten aus.
Geben Sie den Pfad der ausführbaren Visual Studio-Datei (devenv.exe) ein.
Auf einem 32-Bit-System ist der Standardpfad C:\Programme\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe, und auf einem 64-Bit-System ist der Pfad C:\Programme (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe.
Geben Sie im Feld Befehlszeilenargumente die Zeichenfolge /rootsuffix Exp ein.
Hinweis
Alle folgenden LightSwitch-Erweiterungsprojekte verwenden standardmäßig ebenfalls diese Einstellung.
So testen Sie die Datenquellenerweiterung
Klicken Sie in der Menüleiste auf Debuggen und dann auf Debuggen starten. Eine experimentelle Instanz von Visual Studio wird geöffnet.
Wählen Sie in der experimentellen Instanz in der Menüleiste Datei, Neu und Projekt aus.
Erweitern Sie im Dialogfeld Neues Projekt den Knoten Visual Basic oder Visual C#, wählen Sie dann den Knoten LightSwitch und nachfolgend die Vorlage LightSwitch-Desktopanwendung aus.
Geben Sie im Feld Name die Zeichenfolge DataSourceTest ein. Wählen Sie dann die Schaltfläche OK aus, um ein Testprojekt zu erstellen.
Wählen Sie in der Menüleiste Projekt und die Option für Eigenschaften von DataSourceTest aus.
Aktivieren Sie im Projekt-Designer auf der Registerkarte Erweiterungen das Kontrollkästchen DataSourceExtension.
Wählen Sie in der Menüleiste die Optionen Projekt und Datenquelle hinzufügen aus.
Wählen Sie im Assistenten zum Hinzufügen von Datenquellen die Option WCF RIA-Dienst aus, und wählen Sie dann die Schaltfläche Weiter.
Wählen Sie DataSourceExtension.DataSources.XMLDataSource und dann Weiter aus.
Erweitern Sie den Knoten Entitäten, und wählen Sie Product und ProductCategory aus.
Im Feld Verbindungszeichenfolge geben Sie C:\Temp\Products.xml ein und wählen Fertig stellen aus.
Die Datenquelle wird dem Knoten Datenquellen im Projektmappen-Explorer hinzugefügt. Beachten Sie, dass die Tabellen ProductCategory und Products angezeigt werden und die Abfragen GetProductByID und GetProductsByCategory unter Products abgebildet sind.
Erstellen Sie eine Anwendung mit Bildschirmen zur Verwendung der Tabellen und Abfragen. Führen Sie die Anwendung anschließend aus, um das Verhalten zu prüfen.
Beachten Sie, dass die Datenquelle wie jede andere Datenquelle funktioniert. Dies ermöglicht es Ihnen, Daten hinzuzufügen, zu löschen und zu aktualisieren.
Nächste Schritte
Damit ist die exemplarische Vorgehensweise für die Datenquelle beendet. Sie sollten nun über eine voll funktionsfähige Datenquellenerweiterung verfügen, die Sie in jedem LightSwitch-Projekt einsetzen können. Das war nur ein Beispiel für eine Datenquelle, möglicherweise möchten Sie eine Datenquelle erstellen, die auf einen anderen Datentyp zugreift. Dabei gelten die gleichen Schritte und Grundsätze.
Wenn Sie die Erweiterung verteilen möchten, sind einige zusätzliche Schritte erforderlich. Um die Richtigkeit der Informationen sicherzustellen, die für die Erweiterung im Projekt-Designer und Erweiterungs-Manager angezeigt werden, können die Eigenschaften für das VSIX-Paket aktualisiert werden. Weitere Informationen finden Sie unter Gewusst wie: VSIX-Paketeigenschaften. Außerdem sollten bei einer geplanten öffentlichen Verteilung der Erweiterung einige Punkte berücksichtigt werden. Weitere Informationen finden Sie unter Gewusst wie: Verteilen einer LightSwitch-Erweiterung.
Siehe auch
Aufgaben
Gewusst wie: VSIX-Paketeigenschaften
Gewusst wie: Verteilen einer LightSwitch-Erweiterung