Wellframe ist ein Fullstack Software Developing Framework, das anhand von Datenbankdefinitionen (Tabllen, Spalten, Bezüge, Views) ein vollwertiges Framework gemäß der MVC Richtlinie generiert. Die Generierung ist Templates gesteuert und kann in mehrere Zielsprachen (wie C#, Java) generiert werden. Hauptfocus ist jedoch C# und .Net.
Das Framework basiert auf den modernsten Technologien und erstellt automatisch eine Umgebung, die eine Daten- bzw. Business Schicht enthält, darauf basierend wird automatisch ein (u. A. WCF oder .Net Core) Webservice erstellt, dessen Eigenschaften bereits im Vorfeld mit Hilfe von Meta-Informationen gesteuert werden.
Für die Präsentationsschicht stellt das Framework elementare Werkzeuge bzw. eine fertige Webentwicklungsumgebung in HTML5, CSS3 und Javascript (SPA) zur Verfügung. Die Webentwicklungsumgebung enthält ebenfalls eine Umgebung mit Basisfunktionen zur Erstellung einer Android-App.
Die Android-App fungiert als Hülle (ähnlich wie Phone-Gaps) für die Steuerung der Webanwendung und kann auf Funktionen des Android-Betriebssystems zugreifen.
Hauptfocus im bereitgestellten Framework ist eine Entwicklungsumgebung aufzubauen, die elementare Funktionen zur modernen Softwareentwicklung bereitstellt, ohne von Zusatztools abhängig zu sein. So können eigene Anforderungen besser skaliert und alte Projekte besser portiert werden.
Komponenten
Die wichtigste Funktion des Frameworks ist seine dynamische und transparente Funktionsweise, dies wird erst ersichtlich, wenn alle Komponenten des Framworks zusammen benutzt werden. Damit aber die Funktionsweise umfänglich vorgestellt wird, werden hier die Komponenten einzeln mit Beispielen vorgestellt.
Folgendes Flussdiagramm illustriert die Funktionsweise des Wellcode Frameworks:
Zuletzt wird dann ein Beispiel vorgestellt, das alle Komponenten zusamen in einer komplette Entwicklungsumgebung einsetzt.
WellFrame.dll
Um mit dem WellFrame zu arbeiten, benötigt man die Bibliothek WellFrame.dll, darin sind alle nötigen Funktionen enthalten, die das Arbeiten mit dem Framework ermöglichen.
Ein Beispiel kann hier heruntergeladen werden. Dieses Beispiel benutzt das Framework WellFrame, um die Person aus der Microsoft Beispieldatenbank AdventureWorks abzubilden und einige Routinen darauf auszuführen.
Die AdventureWorks Datenbank können Sie bei Microsoft oder Codeplex hier herunterladen oder die Person-Tabelle in einer Datenbank selbst mit folgendem Kommando anlegen
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
CREATE TABLE [Person].[Person]( [BusinessEntityID] [int] NOT NULL, [PersonType] [nchar](2) NOT NULL, [NameStyle] [dbo].[NameStyle] NOT NULL CONSTRAINT [DF_Person_NameStyle] DEFAULT ((0)), [Title] [nvarchar](8) NULL, [FirstName] [dbo].[Name] NOT NULL, [MiddleName] [dbo].[Name] NULL, [LastName] [dbo].[Name] NOT NULL, [Suffix] [nvarchar](10) NULL, [EmailPromotion] [int] NOT NULL CONSTRAINT [DF_Person_EmailPromotion] DEFAULT ((0)), [AdditionalContactInfo] [xml](CONTENT [Person].[AdditionalContactInfoSchemaCollection]) NULL, [Demographics] [xml](CONTENT [Person].[IndividualSurveySchemaCollection]) NULL, [rowguid] [uniqueidentifier] ROWGUIDCOL NOT NULL CONSTRAINT [DF_Person_rowguid] DEFAULT (newid()), [ModifiedDate] [datetime] NOT NULL CONSTRAINT [DF_Person_ModifiedDate] DEFAULT (getdate()), CONSTRAINT [PK_Person_BusinessEntityID] PRIMARY KEY CLUSTERED ( [BusinessEntityID] ASC ) ) |
Folgende Schritte sind zu befolgen, um das WellFrame in Ihrer Applikation einzubinden:
-
- Laden Sie die Libraries zu WellFrame aus diesem Link herunter. Die DLLs sind für AnyCPU kompiliert.
- Fügen Sie Verweise in Ihrem Projekt auf WellFrame.dll, Basics.dll und DbmsLibrary.dll hinzu.
- Die abstrakte Klasse EntityManager muss im Projekt abgeleitet werden, über diese Klasse könne Entities gruppiert instanziert, geändert oder gelöscht werden.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Wellcode.Frame; namespace AdventureWorks { public partial class AdvEntityManager : EntityManager { /// <summary> /// this static instance provides functions for all new generated entities in the class library /// </summary> public static new AdvEntityManager Manager { get { if (EntityManager._Manager == null) EntityManager._Manager = new AdvEntityManager(); return (AdvEntityManager)EntityManager._Manager; } } } } |
Entity
Diese präsentiert sinngemäß ein Objekt und dessen Eigenschaften. In unserem Beispiel wäre dies die Klasse Person. Die Person enthält Properties, wie FirstName, LastName, etc.
Hier ist zu notieren, dass WellFrame die Werte der Entity-Eigenschaften in eine Auflistung speichert. Die Feldnamen der ursprünglichen Tabellen werden hierfür verwendet. Dies ermöglich die Nutzung der Entity auch dann, wenn z. B. Datenbankfelder nicht in der Klasse abgebildet sind (Beispiel hierfür: benutzerdefinierte Felder).
Eine Definition sieht wie folgt aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
/// <summary> /// Person / Person - Entity /// </summary> [DataContract] public partial class Person : WebEntity { #region Members /// <summary> /// BusinessEntityId / Person.BusinessEntityID /// </summary> /// <returns>int?</returns> [DataMember] public virtual int? BusinessEntityId { get { return (int?)this["BusinessEntityID"]; } set { this.Add("BusinessEntityID", value, typeof(int?)); } } /// <summary> /// FirstName / Person.FirstName /// </summary> /// <returns>string</returns> [DataMember] public virtual string FirstName { get { return (string)this["FirstName"]; } set { this.Add("FirstName", value, typeof(string)); } } /// <summary> /// LastName / Person.LastName /// </summary> /// <returns>string</returns> [DataMember] public virtual string LastName { get { return (string)this["LastName"]; } set { this.Add("LastName", value, typeof(string)); } } /// etc. #endregion } // Person |
EntityList
Dies präsentiert eine Auflistung einer Entity. Die Basisklasse für die Auflistung heißt WebEntityList In unserem Beispiel würde die Klasse PersonList heißen und ist wie folgt definiert:
1 2 3 4 5 6 7 8 |
/// <summary> /// Person / PersonList - List /// </summary> [DataContract] public partial class PersonList : WebEntityList<Person> { } |
Factory
Zu jeder Entity muss eine Factory-Klasse definiert werden. Die Factory-Klasse enthält die Fachlogik und die Manipulation einer Entity.
Die Basisklasse für die Factory heißt WebEntityFactory und nimmt sinnesgemäß zwei Template-Klassen an, die Entity und deren Auflistung.
In unserem Beispiel wäre die Factory-Klasse für die Person PersonFactory. In der Factory können diverse Funktion überschrieben oder neu hinzugefügt werden. Die Definition der Klasse PersonFactory kann wie folgt aussehen. Die Definition lehnt sich maßgeblich auf die Primärschlüssel in der Tabelle Person an:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
/// <summary> /// Person - Person - Factory /// </summary> public partial class PersonFactory : WebEntityFactory<Person, PersonList> { /// <summary> /// Factory Intialisierung. /// Tabellenname = Person, /// Felder = "BusinessEntityID" /// Query = ... /// </summary> private PersonFactory(EntityManager manager) { this.Manager = manager; this.TableSchema = "Person"; this.TableName = "Person"; this.Fields = new string[] { "BusinessEntityID" }; this.Query = null; this.Database = DatabaseEnum.Standard; } internal static PersonFactory CreateFactory(EntityManager manager) { return new PersonFactory(manager); } /// <summary> /// Erstellt eine neue instanz zu Person /// </summary> /// <returns>Neue instanz zu Person, falls erfolgreich</returns> public override Person CreateEntity() { var item = new Person(); // Änderungen an den EntityManager melden item.PropertyChanging += this.Manager.WebEntity_PropertyChanging; item.PropertyChanged += this.Manager.WebEntity_PropertyChanged; item.AquireEntityManager += this.Manager.AquireEntityManager; return item; } /// <summary> /// statische Methode für GetEntity /// </summary> /// <returns>Entity, falls vorhanden</returns> public Person GetItem(int? businessEntityId) { return this.GetEntity(0, businessEntityId); } /// <summary> /// statische Methode für GetEntity mit Angabe einer Db-Connection /// </summary> /// <returns>Entity, falls vorhanden</returns> public Person GetItem(DbConnector conn, int? businessEntityId) { return this.GetEntity(conn, 0, businessEntityId); } } |
EntityManager
in einer automatisierten Umgebung würde der EntityManager automatisch um diverse Funktion zur Manipulation einer Entity ergänzt. In unserem Beispiel müssen wir jedoch diese zunächst manuell hinzufügen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
#region Einträge für EntityManager /* /// <summary> /// AdvEntityManager Teil zu PersonFactory / Person / Person. /// </summary> */ public partial class AdvEntityManager { /// <summary> /// PersonFactory zu Person / Person. /// </summary> public PersonFactory PersonFactory { get { if (_PersonFactory == null) { _PersonFactory = global::AdventureWorks.PersonFactory.CreateFactory(this); // register factory to the entity manager this.Factories.Add(_PersonFactory, typeof(Person)); } return _PersonFactory; } } private PersonFactory _PersonFactory = null; /// <summary> /// GetPerson zu Person / Person /// Holen einer Entity über ihre Primary-Key-Felder /// </summary> /// <returns>Entity, falls vorhanden</returns> public Person GetPerson(int? businessEntityId) { return this.PersonFactory.GetEntity(0, businessEntityId); } /// <summary> /// GetPerson zu Person / Person /// Holen einer Entity über ihre Primary-Key-Felder mit Angabe einer Db /// </summary> /// <returns>Entity, falls vorhanden</returns> public Person GetPerson(DbConnector conn, int? businessEntityId) { return this.PersonFactory.GetEntity(conn, businessEntityId); } /// <summary> /// GetPersonList zu Person / Person /// Eine Liste von Person mit einer Where-Klausel zurückgeben /// </summary> /// <returns>Liste möglicher Entities</returns> public PersonList GetPersonList(SqlWhereClause where) { return this.PersonFactory.GetEntities(where); } /// <summary> /// GetPersonList zu Person / Person /// Gesamten Inhalt von Person zurückgeben /// </summary> /// <returns>Liste möglicher Entities</returns> public PersonList GetPersonList() { var where = new SqlWhereClause(); return this.PersonFactory.GetEntities(where); } /// <summary> /// SavePerson zu Person / Person /// Speichern/Hinzufügen einer Entity /// </summary> /// <returns>Entity, falls vorhanden</returns> public Person SavePerson(Person item) { return this.PersonFactory.SaveEntity(item); } /// <summary> /// SavePerson zu Person / Person /// Speichern/Hinzufügen einer Entity mit Angabe einer Db-Connection /// </summary> /// <returns>Entity, falls vorhanden</returns> public Person SavePerson(DbConnector conn, Person item) { return this.PersonFactory.SaveEntity(conn, item); } /// <summary> /// RemovePerson zu Person / Person /// Delete an entity object /// </summary> /// <returns>true, if removed</returns> public bool RemovePerson(Person item) { return this.PersonFactory.RemoveEntity(item); } /// <summary> /// RemovePerson zu Person / Person /// Delete an entity object /// </summary> /// <returns>true, if removed</returns> public bool RemovePerson(DbConnector conn, Person item) { return this.PersonFactory.RemoveEntity(conn, item); } /// <summary> /// Speichert eine Auflistung von PersonList. /// Als gelöscht markierte Entities werden endgültig gelöscht /// </summary> /// <param name="items"></param> /// <returns></returns> public int SavePersonList(PersonList items) { return this.PersonFactory.SaveEntities(items); } } #endregion |
Zusammengefasst werden für die Klassen Entity, EntityList, EntityFactory, EntityManager zwei Dateien erstellt. Die Klassen selbst müssen dann partiell definiert werden.
Das hat den Hintergrund, dass bei voller Ausschöpfung des Frameworks die Dateien „*.Designer.cs“ automatisch erstellt werden. Dafür ist ein ausfürhliches Beispiel unter diesem Link gepostet.In unserem Beispiel werden folgende Dateien erzeugt:
- Die Datei Person.Designer.cs enthält alle Properties der Person, wie FirstName, LastName, etc.
Weiterhin enthält die Datei partielle Definition für der EntityManager, wie z. B. das Erstellen einer Instanz. - Die zweite Datei Person.cs ist das sogenannte benutzerdefinierte Teil und kann die Fachlogik und einige Überschreibungen der Basisklassen enthalten.
Nun führt man alles zusammen und möchte z. B. alle Personen einlesen. Zunächst wird ein ConnectionString zu der AdventureWorks-Datenbank zusammengestellt, wie
1 |
Data Source=.\SQL2014;Initial Catalog=AdventureWorks2014;Integrated Security=True |
Dann können alle Personen mit folgendem Befehl ausgelesen werden
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Wellcode.Basics.WelloConfig.Instanz.DbConnection.ConnectionString = "Data Source=.\SQL2014;Initial Catalog=AdventureWorks2014;Integrated Security=True"; // get all persons var persons = AdvEntityManager.Manager.GetPersonList(); foreach(var person in persons) { Console.WriteLine(person.FirstName + " " + person.LastName); } // change a person name persons[0].FirstName = "changed using WillFrame"; // save the changes AdvEntityManager.SavePerson(persons[0]); // another way to save changes AdvEntityManager.SaveAll(); |
Das komplette Beispiel kann hier heruntergeladen werden. Es ist mit Visual Studio 2013 und dem .Net Framework 4.5 erstellt.