| 

.NET C# Java Javascript Exception

2
Hallo zusammen,
nachdem ich in der dotnetpro gerade gelesen habe, dass es diese neue Plattform gibt, versuche ich mal gleich mein Glück mit einer kniffligen Frage zu Silverlight, Domain Services und Datenbankschemas. Übrigens eine gute Sache mit diesem Forum.

Wir Programmieren derzeit Silverlight 4 - Datenbank-Anwendung, die zukünftig als SaaS Anwendung arbeiten soll. Genau genommen, heißt das, dass viele verschiedene Benutzer sich eine Datenbank 8SQL Server 2008) teilen, aber jeder Benutzer ein eigenes Schema in der Datenbank hat (in der Art CREATE SCHEMA BenutzerSchema AUTHORIZATION Benutzer).

Die Tabellen im Schema haben alle den selben Namen, so dass die Anwendung nur das Schema setzen muss bevor es auf die Tabellen zugreift. Entnommen ist das Konzept einem Artikel auf MSDN http://msdn.microsoft.com/en-us/library/aa479086.aspx und nennt sich "Multi-Tenant Data Architecture" mit "Shared Database, Separate Schemas".

So weit so gut. Das Problem ist, wie ich den Namen des Schemas im Domain Service der Anwendung mitgeben kann. Die SL-Anwendung nutzt WCF-RIA um per Domain Services die Schnittstelle zur Datenbank herzustellen.

Das sieht dann zur Zeit z. B: so aus:

public partial class TabLiveDomainService : LinqToEntitiesDomainService<TabLiveEntities> 
{

/*************************************************/
/********************* CUSTOMER ******************/
/*************************************************/

// TODO:
// Consider constraining the results of your query method. If you need additional input you can
// add parameters to this method or create additional query methods with different names.
// To support paging you will need to add ordering to the 'CustomerTable' query.
public IQueryable<CustomerObject> GetCustomerTable()
{
return this.ObjectContext.CustomerTable.OrderByDescending(p => p.Title);
}

public void InsertCustomerObject(CustomerObject customerObject)
{
if ((customerObject.EntityState != EntityState.Detached))
{
this.ObjectContext.ObjectStateManager.ChangeObjectState(customerObject, EntityState.Added);
}
else
{
this.ObjectContext.CustomerTable.AddObject(customerObject);
}
}



In der Methode GetCustomerTable müsste ich nun das Schema mitgeben (so in der Art[user4711].CustomerTable), so dass jeder User nur auf seine Daten zugreifen kann.

Ich habe auch nur einen User für alle Anwender mit dem ich auf die Datenbank zugreifen kann, so dass ich leider nicht einfach nur das default_schema des datenbank-users setzen kann.

So das wars, ich freue mich auf Eure Hinweise.

Dirk
News:
18.02.2011
ampersand 31 4
3 Antworten
2
Du willst uns hier wirklich testen, oder? ;-)

Wenn ich das Problem richtig verstehe, entsteht Dein Problem eigentlich nur deswegen:
Ich habe auch nur einen User für alle Anwender mit dem ich auf die Datenbank zugreifen kann, so dass ich leider nicht einfach nur das default_schema des datenbank-users setzen kann.

Somit müsstest Du also Deinem ORM mitteilen, dass jedem Tabellennamen immer ein Prefix vorangestellt werden muss. Wenn dieser das unterstützt - und zwar dynamisch - dann hast Du gewonnen. Ich bin jetzt nicht der Entity Framework Spezialist und kann nicht sagen, ob es diese dynamischen Prefixe oder möglicherweise sogar Schemas unterstützt aber es wäre evtl. auch möglich, dass das EF gar nicht das richtige Werkzeug für die Aufgabenstellung ist.

Kann Dir leider keine Musterlösung oder Alternativen anbieten aber möglicherweise hilft mein "Senf" ja bei der Lösungsfindung.
18.02.2011
Torsten Weber 691 1 8
2
Interessante Frage...

Du verwendest das Entity Framework, also einen OR-Mapper. Was Du willst, läuft darauf hinaus, zur Laufzeit zu bestimmen, auf welches Schema Deine Objekte gemappt werden sollen. Das ist ein ziemlich heißes Pflaster, vor allem, da es so aussieht, als ob Du möglicherweise mit der selben ObjectContext-Instanz nacheinander auf verschiedene Schemas zugreifen möchtest (habe leider nicht im Blick, wie lange der ObjectContext bei den RIA-Services lebt und kenne auch Deine Anwendung nicht). Was ist, wenn es in verschiedenen Schemas jeweils einen Customer mit dem Primärschlüssel 42 gibt, der ObjectContext das zugehörige Objekt aber zwischenspeichert?

Es scheint aber mit etwas Trickserei trotzdem zu gehen, wenn es denn sein muß:

Change Entity Framework storage DB schema in runtime

Manipulation eines privaten Feldes - on your own risk...

Ein anderer Ansatz könnte sein, dass Du jedes Mal, wenn Du einen neuen Mandanten im System anlegst, die Mapping-Konfiguration kopierst, darin das Schema änderst und dann in der Anwendung dafür sorgst, dass für jeden Mandanten ein eigener ObjectContext instanziiert wird, jeweils basierend auf seinen Mandanten-spezifischen Mapping-Daten. Auf der einen Seite sicher ein Mehraufwand, auf der anderen Seite entfällt das ständige Umkonfigurieren des Entity Frameworks. Ob und wenn ja wie das mit den WCF-RIA-Services geht, weiß ich leider nicht (würde mich aber auch interessieren).

Du schreibst am Ende Deiner Frage von „jeder User“ – ich hab das so gelesen, dass mit User hier der Mandant (tenant) gemeint ist, da Du ja auf einen Artikel zu Datenbankaspekten von Mandantenfähigkeit verlinkt hast.

--

Edit, nachdem Dirk (ampersand) die Situation noch weiter beschrieben hat:

Ich habe leider mit dem Entity Framework noch nie selbst gearbeitet. Meine ORM-Erfahrungen habe ich mit Hibernate gemacht. Ich nehme deshalb nur an, dass es mit dem EF möglich ist, die Mapping-Konfiguration auch außerhalb einer Assembly (als normale XML-Files auf dem Server) abzulegen. Das müsstest Du selbst evaluieren. Ich habe das auf jeden Fall so gemeint, dass es pro Mandant eine Mapping-Konfiguration für das EF gibt und beim Aufruf des Service ausgewählt wird, mit welcher davon die Persistenzschicht instanziiert wird.

In dem MSDN-Artikel wird auch der „Shared Schema“-Ansatz besprochen. Habt ihr den mal erwogen? Es würde mich wundern, wenn fachliche Anforderungen dagegen sprechen würden (und wenn doch, würde mich der niedrige Preis wundern). Technisch wollt ihr vielleicht die Erweiterung der Tabellen um je eine Mandanten-ID und die entsprechende Abfrage der Queries vermeiden. Am Ende könnte das aber das kleinere Übel sein und sollte sich mit dem EF auch leichter machen lassen als die anderen Varianten. Über die Schwierigkeiten und Risiken Deiner Variante 1) habe ich ja schon gesprochen. Variante 2) schätze ich so ein, dass sie mit dem EF eher noch mehr Schwierigkeiten macht als eigene Schemas, zumindest verwaltungstechnisch schlecht skaliert und keinen erkennbaren Vorteil bietet. Variante 3) hat die von Dir beschriebenen Nachteile beim Hosting und ist auch technisch kein Selbstläufer. Das Stichwort wäre hier „impersonation“, und wie gut WCF-RIA und EF das unterstützen, wäre ich mir jedenfalls nicht sicher.

Was ihr da vorhabt ist technisch/architektonisch auf jeden Fall keine Kleinigkeit und in der Konstellation (SL 4 + WCF-RIA + EF) bestimmt auch noch nicht oft gemacht worden. Wenn ihr das sauber am Laufen habt, wäre das auf jeden Fall einen Artikel in der dotnetpro wert (an den vielleicht mitlesenden Tilman Börner: mich würde das jedenfalls interessieren). Hier steckt weit mehr dahinter als nur ein Implementierungsdetail zu WCF-RIA-Domainservices.
18.02.2011
Matthias Hlawatsch 13,2k 4 9
"Ziemlich heißes Pflaster" trifft es hier sehr genau und das wollte ich durch diesen Kommentar nur nochmals unterstreichen. Ich würde es jedenfalls vermeiden irgendwie zu tricksen, wenn unterm strich kein Ergebnis heraus kommt, das rock-solid ist und auch einem Update des Entity Framework standhält.
Torsten Weber 19.02.2011
Du hast Recht, "jeder User" sollte hier jeder "Benutzer" im Sinne von Mandant oder Kunde, der einen SaaS-Vertrag abgeschlossen hat, heißen.
ampersand 19.02.2011
Die Mapping-Konfiguration liegt im .edmx file in xml Form. Ob man diese tatsächlich in ein externes File auslagern und dynamisch je nach User, das richtige Einladen kann, muss ich noch untersuchen. Auf Anhieb konnte ich die Datei im Deploy Package nicht finden. Eine Ide wäre es auf jeden Fall.
ampersand 23.02.2011
Bzgl. des Shared Schema Ansatzes: Das Problem ist hierbei aus unserer Sicht, dass es in der Praxis immer wieder mal vorkommen wird, dass die Daten eines Users durch ein backup ersetzt werden müssen. Beim Shared Schema bedeutet das faktisch, dass alle anderen User auf dieser Datenbank auch betroffen sind, da beim Löschen der alten Datensätze des Users und beim Einladen der Backup-Datensätze die tabelle faktisch gesperrt werden muss - oder zumindest die Indizes temporär abgehängt werden und danach neu generiert werden müssen.
ampersand 23.02.2011
Fortsetzung vorheriger Kommentar:

Das wäre für uns nur dann eine Lösung, wenn alle anderen Wege definitiv nicht funktionieren.
Zudem mögen es einige Kunden nicht, wenn ihre Daten in den selben Tabellen liegen wie die Daten anderer Benutzer. Da bietet die Schema-Tabellen Variante schon etwas mehr Sicherheit und erleichtert uns die Administration.
ampersand 23.02.2011
Zur Umstellung unserer lokalen Windows-Software auf ein SaaS modell haben wir lange Für und Wider diskutiert und uns schließlich für SL4 entschieden. WCF-RIA und EF sind die ersten Schritte, eine DB-Anbindung zu bauen, die sich für ein kleines Team wie unseres einfach pflegen lässt. Ob die Lösung sinnvoll und umsetzbar ist, werden wir versuchen herauszufinden. Die Clientseitige Anwendungsentwicklung mit SL4 ist jedenfalls sehr produktiv und war daher richtig. Der DB-Zugriff muss noch sinnvoll gelöst werden. Wenn wir das schaffen ob mit oder ohne EF, können wir gerne in der dotnetpro berichten.
ampersand 23.02.2011
0
Hallo Thorsten und Matthias,

vielen Dank für Eure Gedanken und Hinweise. Das bringt mich weiter, weil es das Problem nochmal von anderen Seiten beleuchtet. Ich will aber nochmal ein paar mehr Hintergrundinformationen geben.

Es ist richtig, wie Torsten geschrieben hat, dass das Problem entsteht, weil ich nur einen Datenbankuser habe, mit dem ich auf die Datenbank zugreifen kann. Warum das derzeit so ist: Unser Serverhoster lässt nur einen User je Datenbank zu.

Okay jetzt könnte man denken, dann nimm halt mehr Datenbanken. Das ist allerdings eine Kostenfrage. Wie geschrieben erstellen wir eine SaaS Anwendung, d.h. sehr viele Benutzer mieten unsere Software monatlich. Der Preis ist aber relativ niedrig, sagen wir mal 5 EUR im Monat. Ein gehosteter Windows-Server mit Datenbank kostet aber je nach Anbieter zwischen 30 und 150 EUR je Monat. Für einen Benutzer je Datenbank damit natürlich indiskutabel.

Die Grundidee war daher das oben verlinkte "Multi-Tenant Data Architecture" mit "Shared Database, Separate Schemas" zu implementieren. Wenn dann 50 Benutzer sich eine Datenbank teilen, dann ist es kostenmäßig abgedeckt.

Es bleiben also aus meiner Sicht folgende Optionen:
1. Die Anwendung müsste einen Prefix vor den Tabellen mitgeben, also das oben angesprochene Schema in der Datenbank, z. B. SChema_Benutzer1.CustomerTable, Schema_Benutzer2.CustomerTable

2. Die Anwendung müsste je nach Benutzer auf benutzerspezifisch benannte Tabellen zugreifen, z. B. Benutzer1_CustomerTable, Benutzer2_CustomerTable etc.

3. Wir suchen einen Hoster, der beliebig viele Benutzer oder beliebig viele Datenbanken zu einem tragbaren Preis bietet.


Zu 1: Das scheint mit LinqToEntity nicht zu gehen, wäre aber die sauberste Lösung, da sie auch einen vernünftigen Sicherheitslevel bietet. WEnn das Schema mal gesetzt ist, ist ein Zugriff auf die DAten anderer Benutzer praktisch ausgeschlossen.

Ich habe an anderer Stelle den Vorschlag bekommen, stattdessen für die Erstellung des DomainServices LinqToSQL zu verwenden. Soweit ich weiß, soll es aber LinqToSQL nicht mehr lange geben, bzw durch LinqToEntity vollständig ersetzt werden.

Zu 2: Der Utnerschied zu 1 ist, dass tatsächlich der Tabellenname mit einer benutzerID versehen ist, also ohne Schema. Technisch ist die Anlage dieser Tabellen kein Problem (Wenn der Benutzer dedn Mietvertrag abschließt werden im Hitnergrund die Tabellen erstellt. Aber natürlich kennt die Anwendung nicht die Tabellennamen, d.h. wir müssten die Anwendung neu kompilieren, was natürlich nicht für jeden Benutzer möglich ist oder wir mssten die Tabellennamen von außen in die Anwendung geben, was nach "angreifbar" riecht.

Zu 3: Wäre natürlich das einfachste. Statt des "Shared Database, Separate Schemas" würden wir also "Separate Databases" nehmen. Wir haben aber bisher nur einen Anbieter gefunden, der das bietet, aber mit Sitz USA, was mir aus Performancesicht Sorgen macht.

Matthias, was Du geschrieben hast, bzgl. "ObjectContext-Instanz nacheinander auf verschiedene Schemas zugreifen" hat mich etwas aufgeschreckt. Ist das tatsächlich so? Die Anwendung liegt in XAP-Form auf dem Server. Wenn der Benutzer die Anwendung öffnet, wird die XaP heruntergeladen und die Anwendung gestartet. Teilt er sich den objectContext tatsächlich mit einem anderen benutzer, der ebenfalls die XAP heruntergeladen und die Anwendung gestart hat?

Das Problem mit dem gleichen Primärschlüssel z. B. für einen Kundenstamm sollte eigentlich keines sein. Durch die Voranstellung des Schemas sollte der Benutzer nur seine eigenen Daten im Zugriff haben. Es gibt ja tatsächlich je Benutzer eine eigene CustomerTable in Ihrem eigenen Schema. Zudem sind unsere Primärschlüssel Guid's, so dass aus dieser Sicht auch eine gewisse, wenn auch nicht 100% Sicherheit besteht.

Der verlinkte Artikel ist interessant. Ich muss mir das mal genauer ansehen.

Der zweite Vorschlag die Mapping Konfiguration zu kopieren, das Schema zu ändern und dann wieder in die Anwendung zu geben, ist, wenn ich Dich richtig verstanden habe, möglich, aber muss man dann nicht jedesmall die Assembly neu kompilieren? Würde mich freuen, wenn Du da nochmal sagen könntest, wie Du es gemeint hast.

Danke auf jeden Fall nochmal für Eure Hinweise.

Dirk
19.02.2011
ampersand 31 4
Hallo Dirk, so wie ich diese Plattform hier verstehe, sind „Antworten“ gemeint als „Antworten auf / Lösungen für die gestellte Frage“. Für „Antworten auf Antworten“ sind die Kommentare gedacht. Von daher wäre es vielleicht besser gewesen, Du hättest einen Teil Deines Textes als Ergänzung Deiner ursprünglichen Frage angefügt (per Edit-Funktion) und den Rest als Kommentare zu den Antworten von Torsten und mir.
Matthias Hlawatsch 19.02.2011
1
Zu den XAP-Dateien: wenn ich nicht gerade komplett auf der Leitung stehe, dann läuft Euer DomainService und damit der ObjectContext doch auf dem Server und ist somit nicht Teil der XAP-Datei, sondern des dort deployten WCF-Dienstes. Und dort ist es zumindest theoretisch möglich (z.B. wenn der Dienst als Singleton konfiguriert ist), wenn auch nicht unbedingt wahrscheinlich, dass die gleiche Instanz mehrere User bedient.
Zu den anderen Punkten nehme ich in meiner Antwort Stellung.
Matthias Hlawatsch 19.02.2011
Okay, das war mir nicht klar, dass man nur kommentieren soll oder den Ausgangsbeitrag editieren. Ist schon etwas ungewöhnlich, da man dann sehr lange Beiträge bekommt, die dann auch nicht mehr so richtig in der zeitlichen Abfolge stehen. Ich schreibe mal ein Feedback/Vorschlag für eine bessere Erläuterung der bedienung von Codekicker. in den Feedback-Bereich
ampersand 23.02.2011
Matthias,
Du hast natürlich recht, dass der Domain Service auf dem Server läuft, da stand ich wohl auf dem Schlauch ;-) Das testen wir in Kürze auf einem externen Server, dann werden wir sehen ob der DomainService singleton ist oder nicht. Auf jeden Fall ein wichtiger Hinweis.
ampersand 23.02.2011

Stelle deine .net-Frage jetzt!
TOP TECHNOLOGIES CONSULTING GmbH