Laden...
Avatar #avatar-2482.jpg
TheShihan myCSharp.de - Member
Software Engineer Zürich Dabei seit 25.09.2007 170 Beiträge
Benutzerbeschreibung
Informationen sind auf meinem Blog zu finden oder bei einem persönlichen unverbindlichen Gespräch 😉

Forenbeiträge von TheShihan Ingesamt 170 Beiträge

23.08.2021 - 15:11 Uhr

OK, danke für die Antworten. Ich habe mich nun in die Thematik bezüglich DateTimeOffset eingelesen und etwas damit herumexperimentiert.

Allerdings denke ich, gibt es noch ein Problem.

Grundsätzlich möchte ich ja die Zeit für den Benutzer nach dem auslesen aus der DB wieder in seiner Zeit anzeigen und zwar wo er aktuell ist (gemäss seinem aktuellen Time Zone Offset). Das heisst, wenn er ein Element z.B. in den USA erstellt hat, nun aber in UK ist, soll es im die Zeit entsprechend umgerechnet für UK anzeigen.

So wie ich es verstehe, kann man das ja mit DateTimeOffset.ToLocalTime(), es gibt auch noch das Property DateTimeOffset.LocalDateTime - der Unterschied erschliesst sich mir noch nicht.

Wenn ich das in Blazor (Server) teste, erhalte ich in beiden Fällen die gleiche korrekte Uhrzeit:


<div>
    <h2>DateTimeOffset Testing</h2>
    <p>
        UTC Now to Local Time (Method): @DateTimeOffset.UtcNow.ToLocalTime().ToString("g")
        <br />
        UTC Now to Local Time (Property): @DateTimeOffset.UtcNow.LocalDateTime.ToString("g")
    </p>
</div>

Allerdings ist das erst einmal lokal getestet, ich weiss noch nicht wie sich das auf einem Server in einer anderen Zeitzone verhält.

Das heisst, ich könnte annehmen, dass ich auf das Property "LocalDateTime" binden könnte in meinem Control. Doch kann das wirklich funktionieren? Meine Vermutung ist, dass es dann von UTC einfach in die Zeitzone des Servers konvertiert wird, nicht die des Benutzers. Oder wie war folgender Satz gemeint:
"Du musst nur noch die Zeitzone des Benutzers haben, und das in der Formatierung mit übergeben."
?

Falls ich die Zeitzone des Benutzers nehmen muss und dann z.B. mittels des entsprechenden Offsets das neue DateTime(Offset) erstellen müsste, dann hätte ich ja wieder das ursprüngliche Problem, dass ich irgendwie den _TimeZoneService _in meine Entität bringen müsste.

Mache ich einen Überlegungsfehler?

18.08.2021 - 20:44 Uhr

Ich habe ein Blazor (Server) Projekt, welches das Entity Framework verwendet.

Es ist möglich, gewisse Entitäten zu teilen, sprich mehrere Benutzer haben z.B Rechte auf ein "Entry". Ein Entry hat also nicht direkt ein Benutzermapping (Creator Mapping ist aber da).

Alle Dates (DateTime) möchte ich als UTC in der DB speichern. Denke das ist am sinnvollsten, z.B. für Funktionen wie: gebe mir die 10 zuletzt veröffentlichten Einträge aller Benutzer. Würde ich die Daten in der jeweiligen Benutzerzeit speichern ginge das natürlich nicht. (nur ein Beispiel)

Ich weiss auch wie ich grundsätzlich (mittels JavaScript Interop) die lokale Benutzerzeitzone (resp. das Offset) herausfinde und verwenden kann. Habe es aktuell nach folgendem Beispiel implementiert - das heisst ich habe einen TimeZoneService (registriert als _scoped _ Service im Startup.cs):
Convert DateTime to user's time zone with server-side Blazor - Meziantou's blog

Für die Anzeige/Bearbeitung der Entitäten verwende ich vielfach Radzen-Komponenten, ich glaube aber, dass es bezüglich der Frage keine Rolle spielt, da es ein allgemeines "Problem" darstellt. Die Komponente bindet an ein Property der Entität. Aktuelles Beispiel (siehe _RadzenDatePicker _-> @bind-Value):


<RadzenGridColumn TItem="Entry" Property="Date" Title="Date">
    <Template Context="entry">
        <div>@entry.DateLocal.ToString("g")</div>
    </Template>
    <EditTemplate Context="entry">
        <RadzenDatePicker TValue="DateTime" @bind-Value=@entry.DateLocal ShowTime="true" ShowSeconds="false" HoursStep="1" MinutesStep="1" SecondsStep="10"  DateFormat="g" />
    </EditTemplate>
</RadzenGridColumn>

Wenn die Daten in der DB als UTC gespeichert sind, dann wird sie dem Benutzer so natürlich auch als UTC angezeigt, respektive er editiert UTC Daten, würde diese auf seine lokale Zeitzone ausgerichtet anpassen und somit als (logisch) nicht mehr UTC zurückspeichern.

Ich suche nun nach einem eleganten, sauberen Weg, so dass die DB weiterhin die DateTimes als UTC speichert, der Benutzer aber die Daten in seiner Zeitzone sieht.

Ein Lösungsansatz war, dass ich ein zusätzliches _NotMapped _Property auf der Entität mache, dieses wandelt das Property mit dem UTC-Wert beim Lesen mit Hilfe des _TimeZoneServices _in die lokale Benutzer-Zeitzone um. Beim Schreiben verhält es sich umgekehrt.

Allerdings ist es erstens vermutlich nicht sauber, wenn man etwas in ein Entity injected und zweites war mir dies bisher noch gar nicht möglich. Da ich diesbezüglich den DB-Context erweitert habe (_TimeZoneService _Injection bereits dort, das es so in die Entitäten kommt). Allerdings crasht es dann, da ein Scoped Service nicht im DB-Context Injected werden kann.

Dann dachte ich, dass ich es so machen müsste, dass man diesen Service nicht in der Entität braucht. Dass die Zeitzone z.B. über eine Benutzerprofil-Seite auf dem _ApplicationUser _als Property gespeichert werden könnte. Allerdings bringt mir das nichts, da ja mehrere Benutzer eine Entität verwenden können und es hier keine direkte Beziehung gibt.

Im Internet findet ich merkwürdigerweise für diese Problemstellung keine Lösungen, obwohl ich annehme, dass dies doch ein häufiges Problem sein müsste, auch z.B. in ASP .NET.

Hat wer eine Idee wie man das lösen könnte?

07.05.2010 - 11:27 Uhr

Das habe ich schonmal probiert (hätte ich vielleicht sagen sollen), das funktioniert allerdings nur begrenzt gut 😃

Es nimmt die Umbrüche dann nicht weg, aber es zerstört die Tags des inneren Elementes. Also beim Beispiel wird z.b. dann aus "<add ... " -> "&gt; add ..."

07.05.2010 - 11:20 Uhr

Hallo,

Ich versuche in einer XML-Datei einen Knoten, resp. dessen Inhalt, mit einem anderen Inhalt zu ersetzen. Der Inhalt stammt dabei aus einer Mehrzeiligen TextBox, welche der Benutzer ausfüllen kann.

Ich habe folgende Methode dazu geschrieben:

        public static void ReplaceConfigPart(string path, string tag, string newTagContent)
        {
            // read xml document
            XmlDocument originalXml = new XmlDocument();

            originalXml.Load(path);

            // get the node that shall be replaced
            XmlNode editNode = originalXml.SelectSingleNode(@"//" + tag);

            // prepare new node
            XmlNode newNode = editNode.Clone();
            newNode.InnerXml = newTagContent];

            // Replace the contents of the editNode with the new node.
            editNode.ParentNode.ReplaceChild(newNode, editNode);

            // save changes
            originalXml.Save(path);
        }

path ist der Pfad zur XML-Datei, tag ist der Tag, welcher in der Config ersetzt wird. Nehmen wir als Beispiel "connectionStrings" in der Anwendungskonfigurationsdatei. newTagContent ist der neue Content der in diesen Knoten geschriben werden soll, also in diesem Fall, z.B.:

        <add name="ClientDipsIn.Properties.Settings.DbConnectionStringXy"
            connectionString="Data Source=SERVER;Initial Catalog=DBxy;Persist Security Info=True;User ID=bla;Password=blup"
            providerName="System.Data.SqlClient" />

Das Einfügen funktioniert zwar, jedoch verhaut er mir die Zeilenumbrüche. Das heisst das alles nun auf einer Zeile steht, was ich nicht will.

Leider finde ich keine Option um irgendwie mitzugeben, was er mit den Zeilenumbrüchen machen soll. Ich nehme an, dass vieleicht die Zuweisung zu InnerXml dies verursacht, aber ich finde keinen Weg um das was ich will besser umzusetzen.

Vielleicht hat ja einer von euch einen Tipp?

Danke und Gruss, Shi

15.04.2010 - 13:35 Uhr

Ja, das Argument ist natürlich schon schwach, aber die Leute bei uns sind meistens "Traditionalisten" - kommt mal ne neue Idee fallen sie gleich in Ohmacht 😉 Darum versuche ich nicht immer gegen Windmühlen zu kämpfen.

Habe es nun so gemacht wie FZELLE es beschrieben hat, gemäss Beispiel von: http://msdn.microsoft.com/de-de/library/system.data.sqlclient.sqlcommand.executenonquery%28VS.80%29.aspx

15.04.2010 - 11:00 Uhr

verwendetes Datenbanksystem: MSSQL 2005

Hallo,

Ich verwende .NET 2.0 und ADO.NET mit typisierten DataSets. Für die einzelnen DB-Tabellen habe ich TableAdapter definiert, welche dann die Queries etc. beinhalten für die jeweilige Tabelle.

Nun habe ich aber im Management Studio ein SQL-Skript erstellt, welches gewisse Aktionen ausführt, welches mehrere Tabellen betrifft. Z.B. werden anhand von Daten in Tabelle x in den Tabellen m und n neue Zeilen eingetragen und in anderen Tabellen werden Zeilen z.B. gleichzeitig anhand von den Daten in Tabelle x gelöscht. Also habe ich SELECTs, INSERTS und DELETEs.

Da wir bisher noch keine Stored Procedures haben möchte das ganze aber nicht als SP in der DB speichern. Möchte gerne das Skript im "Code" gespeichert haben, damit wir alles am gleichen Ort haben. Nun weiss ich aber nicht wie ich das am elegantesten einbaue. Ich könnte das natürlich "manuell" einbauen und die DataSets ignorieren (mit SqlCommand etc.), anderseits gibt es vielleicht eine möglichkeit das sauberer zu machen in dem ich das (typisierte) DatSet verwende und das Query dort platziere. Es gibt dort ja die Möglichkeit Queries hinzuzufügen, allerdings kann man dort nur entweder Inserts, Selects oder z.B. Update Queries erstellen, nicht aber gemischte...

Wie würdet ihr das machen?

Gruss, Shi

05.03.2010 - 14:18 Uhr

Wie es aussieht tritt der Fehler auch einfach auf wenn man CTRL+0 drückt (ohne mein umgebastel) um den Wert auf NULL zurückzusetzen. Das Problem scheint nur bei Integer-Spalten aufzutreten.

Wenn man von der Spalte das macht: "DefaultCellStyle.NullValue = System.DBNull.Value" kann man das Problem umgehen. Default ist glaub sonst "string.Empty" was vermutlich die Ursache ist.

Löste bei mir nun zwar noch einige andere Unschönheiten aus, aber die konnte ich auch schon umgehen (DataErrors bei Anzeige von neuen leeren Zeilen).

05.03.2010 - 11:30 Uhr

verwendetes Datenbanksystem: SQL2005

Hallo,

Ich habe ein gebundenes DataGridView, das mir die Werte von Tabelle "A" zeigt.

In diesem DGV hatte es eine Spalte mit dem Type DataGridViewComboBoxColumn, zeigt mir also ComboBoxen in dieser Spalten an. Diese CBs befülle ich mit einigen ausgewählten Werten aus Tabelle "B".

Nun habe ich das Query erweitert, welches mir diese Werte aus Tabelle "B" nimmt, damit ich zusätzlich einen "Default Wert" in der Liste in der ComboBox erhalte, einen leeren String. Z.B. wenn der Benutzer mal was ausgewählt hat, aber später wieder möchte "nichts" auswählen -> in der Tabelle A, darf man in dieser Spalte (CARRIER_TYPE, integer) NULL einfügen.

Das Darstellen klappt auch soweit so gut. Doch wenn ich nun in der ComboBox von einem bestehenden Wert, der nicht NULL ist, sondern z.B. "1" den ersten Eintrag auswähle, damit es mir den Wert in "A" dann auf NULL setzt, erhalte ich gleich folgende Fehlermeldung (DataError Event wird ausgelöst):

{"Die Spalte 'CARRIER_TYPE' kann nicht auf Null gesetzt werden. Verwenden Sie statt dessen DBNull."}

Ich verstehe dass nicht. Beim Query wo ich mir die Daten hole, setze ich wie gesagt den Default-Wert auf NULL, im typisierten DataSet sollte also in dieser Intger-Spalte der Wert System.DBNull sein? Auf "null" kann ich das manuell ja auch nicht setzen, da es wie gesagt "Integer" ist und nicht nullable.

Das typisierte DataSet für die Tabelle "A" erlaubt in dieser Spalte auch NULL Werte und der DefaultValue ist "<DBNull>", das ist noch vom Generieren so und habe nichts angepasst (auch nicht an der DB-Tabelle).

Jemand einen Tipp oder eine Idee?

Danke und Gruss, Shi

05.01.2010 - 16:25 Uhr

"Nooooiiiiiinnnn" 😉

Na gut... dann mach ich mich mal ans umprogrammieren :-\

05.01.2010 - 16:06 Uhr

Hallo,

Zur Zeit lade ich mit folgender Methode Bilder aus einer Datenbank und wandle sie in eine Image Objekt um:


        public static Image CreateImageFromObject(object imageBlob)
        {
            Image img = null;

            if (imageBlob != null)
            {
                // Create stream from object
                byte[] bits = (byte[])imageBlob;
                using (MemoryStream ms = new MemoryStream(bits))
                {
                    ms.Write(bits, 0, bits.Length);

                    try
                    {
                        img = Image.FromStream(ms);
                    }
                    catch (ArgumentException)
                    {
                        throw new MissingDataException("ImageData");
                    }
                }
            }

            return img;
        }

Das funktioniert auch gut, nur gab es nun einen Fall, da wurde eine "OutOfMemoryException" geworfen. Das Problem ist, dass ich 192x A4-Seiten in den Speicher lade (normalerweise sind es in der Regel 1 - 4 Seiten, ist also ein Ausnahmefall). Dann hat es ca. 1,8GB Ram aufgefressen (ganze App) 😃 ich denke so pro Bild sind es 6-8 MB.

Wenn ich die Anwendung umbauen müsste, dass immer nur das zur Zeit angezeigte Bild im Speicher ist, wäre das ein Riesenaufwand... darum wollte ich eigentlich einen anderen Weg wählen, bin aber bisher gescheitert.

Ich versuchte das Bild in dieser Methode mit Image.Save() als JPEG zu konvertieren und das alte Bild dann zu disposen, leider frisst es mir nun nochmehr Speicher. Irgendwo habe ich auch gelesen, dass es eh immer gleich viel Speicher im RAM benötigt ob es jetzt eine Bitmap oder Jpeg ist, da das Bild quasi unkomprimiert im Speicher gehalten wird - in der MSDN konnte ich diesbezüglich jedoch nichts finden.

Hat jemand eine Idee, oder kann die Aussage bezüglich dem Speicher bestätigen (dann kann ich mir das ersparen). Ansonsten muss ich wohl die App umbauen :-\

Gruss, Shi

23.11.2009 - 10:08 Uhr

Ok, Danke nun ist es mir klar (sonst wird sich dass dann in der Praxis zeigen wenn nicht 😉 ).

20.11.2009 - 18:19 Uhr

Ok, aber das mit dem "Build" und "Revision" verstehe ich nicht ganz.

Würde das heissen, das jmd. also max. definiert "Die nächste Version ist 4.2"

Wenn ich dann arbeite und zwischenzeitlich manuell Builde oder via Buildserver wird dann jeweils automatisch die Nummer hochgeschraubt (mit z.b. "4.2.*")?

20.11.2009 - 16:11 Uhr

Hallo zusammen

Ich bräuchte etwas Hilfe oder Hinweise, wie man am besten die Versionsnummerierung im Visual Studio verwendet.

Die Versionsnummer ist ja immer "4-Stellig". Also:
Major.Minor.Build.Revision

Bisher, so professionell wie wir im Moment ja arbeiten 😉, war es immer ungefähr so.

Chef: Bau mal Feature X + Y + Z für den Kunde Mn bis zum xx.xx.xxxx ein und mach dann neue Version zum testen.

Ich habe dann jeweils selbstständig eine neue Versionsnummer kreiert nach dem Schema:

a.b.c.d

wobei
a.b. = eigentlich immer fix, "b" hätte ich geändert für wirklich grosse Veränderungen
d = habe ich raufgetan für kleine Features oder Bugfixes, und c für etwas grössere umfangreichere Features

Da wir langsam und Schrittweise etwas mehr Professionalität wollen führen wir nun mal sogar sowas wie Releaseplanung ein...

Das heisst, dann wohl dass der Chef sagen müsste, am xx.xx.xxxx wird Version Xy mit den Features X + Y + J ausgeliefert.

Ich denke er sollte also nach folgendem Schema verfahren

a = eigentlich immer fix, es sei denn wir machen wirklich ein komplettes Produkt-Relaunch auf einer anderen Technologie oder "from scratch"
b = geändert bei sehr grossen Änderungen, wenn z.b. ganze Module neu dazukommen
c = erhöht für normale kleinere Anpassungen/Features
d = für Patches/Bugfixes

Also das heisst, er definiert ja nur "a.b.c" wobei "d" dann erhöht werden würde, sollten Nachträglich Fehler auftauchen.

Z.B., also wird in unserem Bugtracker (trac) ein Milestone definiert:
"Kunde Xy Produkt Mn 4.1.1.0" und dann werden die Tickets diesem Milestone zugewiesen.

Im Visualstudio würde ich dann dem fertigen Produkt auch diese Nummer geben. Würde es später Bugs geben, würde ich die letzte Zahl "Patchlevel" entsprechend erhöhen.

Ist diese Vorgehen falsch oder was empfiehlt sich hier?

(btw, wir haben noch keinen Build- oder Integrations-Server, aber das wird sich vermutlich auch in einigen Monaten ändern.)

Gruss und Danke, Shi

30.09.2009 - 15:31 Uhr

verwendetes Datenbanksystem: MSSQL 2005

Hallo,

Ich habe ein Problem mit einer Verknüpften Tabelle. Sagen wir ich habe eine Tabelle "Master":

ID | VALUE

Wobei ID, der Primärschlüssel und auf Autoincrement ist.

Dann habe ich eine Verknüpfte Tabelle mit Detailpositionen, nennen wir sie "Detail":

ID | POS | TEXT

Wobei, ID + POS den Primärschlüsselbilden und "ID" ist der Foreign-Key zu "Master.ID". POS ist übrigends auch Autoincrement.

Im Visual Studio (2005, ich verwende das Fx 2.0), habe ich ein typisiertes DataSet in welchem die Beiden Tabellen sind UND auch die Relation dazwischen vorhanden ist (in Eigenschaften Eingestellt auf "nur Beziehung"). Auf einer Form habe ich jetzt zwei DataGrids, eines Für Master und eines für Detail. Erstellt wurde das ganze mehr oder weniger mit Drag und Drop, die Detail-Daten werden auch korrekt angezeigt, je nach gewähltem Datensatz im Master. Das wird ja über die Bindingsource mit Hilfe der Relation gesteuert.

Bestehende Daten, egal ob Master/Detail, kann ich ändern und speichern. Wenn ich nun aber folgendes mache geht es nicht:
Im Master klicke ich in die Zeile für neue Datensätze, es zeigt mir beim klicken dann auch gleich die neue ID des Primärschlüssels an. Ich kann nun aber wieder aus dieser Zeile in eine bestehende Zeile klicken und dann wieder in die Zeile für neue Datensätze, dann wird jeweils die ID um immer 1 erhöht (da er wohl das mit dem Autoincrement abbildet). Nun gut, jetzt habe ich so hin und her geklickt. Jetzt möchte ich für einen neuen Master-Datensatz gleich Details hinzufügen, ohne vorher zu speichern!

Ich geben in der Zeile für neue Datensätze in den Details nun also Werte ein. Es wird mir nun auch in der Spalte "ID" die selbe ID angezeigt wie beim Master-Datensatz. Ist ja auch logisch, da sie zusammengehören.

Wenn ich nun aber auf speichern klicke, dann bekomme ich eine SqlException:

Zusätzliche Informationen: INSERT statement conflicted with COLUMN FOREIGN KEY constraint 'FK_TIC$ACC_PD_POS_TIC$ACC_PD'. The conflict occurred in database 'INV_ZERZE_500', table 'TIC$ACC_PD', column 'PDA_ID'.
The statement has been terminated.

Ich weiss auch warum das passiert, aber nicht wie ich es umgehen kann. Warum es passiert:
Das generierte Insert Query sieht vereinfacht wie folgt aus:

exec sp_executesql N'INSERT INTO [MASTER] ([VALUE]) VALUES (@Value);
SELECT ID, VALUE FROM [MASTER] WHERE (ID = SCOPE_IDENTITY())', N'@Value varchar(4)', @Value = '9999'

Für die Spalte "ID" wird also mittels "SCOPE_IDENTITY())" die ID vergeben. Diese unterscheidet sich nun aber von der, welche im Grid mit dem Hin und Herklicken (was ja beim Arbeit zwangsläufig wirklich passieren kann) angezeigt wird. Anstelle von z.B. ID "31" wie im Grid angezeigt wurde in die DB eine "20" gespeichert.

Nachfolgend speichern nun ADO.NET die Detail-Tabelle. Dummerweise wird nun beim speichern aber in der Spalte "ID" in dieser Tabelle der Wert "31" gespeichert, also der, welcher im Grid angezeigt wurde - resp. der welcher angenommen wurde vor dem Speichern, dass er die zukünftige ID ist. Da die wahre ID nun aber z.B. "20" ist und es gar keinen Wert "31" noch in der Master-Tabelle gibt, fliegt das Query somit auf die Schnauze. 😦

Falls es was hilft, ich speichere wie folgt:

            this.masterBindingSource.EndEdit();
            this.detailBindingSource.EndEdit();
            this.masterTableAdapter.Update(this.dataSet.MasterTable);
            this.detailTableAdapter.Update(this.dataSet.DetailTable);

Weiss einer warum ADO.NET sich so verhält und wie man das gerade biegen kann?

Danke und Gruss, Shihan

20.07.2009 - 16:32 Uhr

Hallo zusammen,

Ich habe mehrere Seiten mit Listen von Medien. Dann habe ich mir eine Detailseite gebastelt, welche für ein Einzelnes Medium (Entity: "Media") die Details anzeigt.

Im Moment habe ich mal Testweise das so gemacht:

  • Objectsource mit Zugriff auf die Klasse/Methode, welche mir pro "MediaId" die Entity liefert, diese Daten zeige ich im Moment in einer DetailView an.

Ein Media-Entity hat folgende Properties (unter anderem):

  • Titel
  • Beschreibung
  • Erstelldatum
  • Bild
    Navigation-Properties:
    Medien-Status -> Entity "MediaState"

Nun, natürlich generiert mir der Wizard nichts für die Bildanzeige und auch die Navigation-Properties werden nicht aufgelöst.

Jetzt frage ich mich:
Da ich die Seite nur zum anzeigen der Daten verwenden will (das wird sich auch nie ändern, Amen 😉 ), soll ich überhaupt eine solche vordefinierte Control verwenden, oder würdet ihr empfehlen so etwas von Hand zu machen (Enitity holen und die Daten aus dem Entity manuell in Labels etc. abfüllen)?

Gruss, Shi

20.07.2009 - 15:22 Uhr

Ich habe jetzt gerade kein VS zur Hand, darum kann ich das nicht mit Sicherheit sagen, aber: wäre es nicht besser anstelle der Spalten, die Zeilen zu formatieren (im Endeffekt die <tr>)?

Wenn die GridView-Eigenschaft sowas nicht zur Verfügung stellen, würde ich mal versuchen ob es mit einem "Margin" "0" für die Spalten was bringt. Evtl. hast du dort ja noch einen Abstand drin.

17.07.2009 - 14:31 Uhr

Danke, habe mir schon sowas ähnliches gedacht, dass ich es damit lösen könnte (mit der übergabe der TitleId), allerdings hätte ich das ganze Objekt geholt 😃 wieder was gelernt.

Btw, ich lese gerade das von dir empfohlene Buch durch "Programming Entity Framework" (Julia Lermann). Bis jetzt sehr empfehlenswert.

16.07.2009 - 13:19 Uhr

Hmm.. ich sehe jetzt nicht gerade was du ansprechen willst. Wenn du meinst dass die View keine User-Objekte kennen soll, dann weiss ich nicht wie ich das sonst machen soll -> ich möchte dieses eher kleine Projekt ja nicht unnötig aufblasen.

Ich denke es ist doch legitim die Werte in der View abzufüllen. Ich möchte ja nicht eine Methode schreiben, im Stil von:

public void UpdateUser(userId, string newUsername, string new Lastname, DateTime newBirthDay, string newEmail, string newAdress, string newCity, int newPlz, string newCountry, string newTelephone, string newFax, string newMobile, string new AlternateEmail, ... );

Oder meinst du was anderes?

16.07.2009 - 00:22 Uhr

Ich brauche leider noch immer etwas Hilfe. Ich habe mal die Architektur aufgeräumt (ich hoffe zum Guten)... jedoch bekomme ich hier langsam Hirnblutungen...

Diese dämlich context Geschichte scheint mir wirklich etwas verkorkst zu sein, ich weiss nicht ob ich jetzt einfach zu dämlich bin, oder ob das EF 1.0 hier einfach noch recht schwach ist.

Das Problem das ich habe/hatte/teilweise immer noch habe:
Ich möchte mir einen bestehenden User anpassen. Im BusinessLogicHandler habe ich eine Methode dir mir für eine ID einen User gibt, in der View fülle ich die Properties dieses User-Entity ab mit den Werten aus Textboxen (z.B. neuer Nachname, Email etc.), dann gebe ich das User-Objekt an eine Methode UpdateUser, welche wieder im BusinessLogicHandler ist.

-> zuerst ging mal gar nichts, hat gar nichts gemekert, aber in der DB kamen die Änderungen nicht an. Mittlerweile geht es, ich habe es nun so gemacht, dass die Methode GetUserById den User "detached" und bei "UpdateUser" wird der User zuerst wieder "attached" dann mit einer Custom-Extension-Method "SetAllModified" werden alle Properties als geändert markiert und der User wird dann via "context.SaveChanges()" gespeichert, siehe:
BusinessLogicHandler (ihr könnt hier das ganze Projekt im SVN Browsern, zum besseren Verständnis).

Ich nehme an das liegt daran, weil der Context im Using verwendet wird. Folglich hängt dieser Context irgendwie noch am Objekt dran wenn ich es dann später versuche zu speichern. Resp. wenn ich es nur mit SaveChanges() speichere und es nicht meinem neuen Context hinzufüge, landen die Änderungen halt im Nirvana.

  1. Gibt es da wirklich keinen besseren Weg?
  2. Leider habe ich nach wie vor den Titel zu speichern, normale Properties werden mit diesem gemurkse nun upgedated, jedoch der "Title" kann ich einfach nicht mitabspeichern, es kommt aber auch kein Fehler... wtf?!

Ehrlich... langsam sehne ich mich nach meinen typisierten DataSets zurück 😦

14.07.2009 - 13:37 Uhr

Hallo,

Eventuell kann mir einer von euch ja einen Tipp geben betreffend einem simplen C#-Logging-"Framework"/Klasse/WasAuchImmer..

Was ich möchte ist das Folgende:

  • Loggen in eine Datei (z.B. fix auf "Xy.log")
  • Loggen in eine Datei (je nach Datum (z.B. pro Tag eine neue Datei)
  • Loggen in die Console

Ich würde gerne dann innerhalb der Anwendung die jeweiligen Logger instanzieren und gleich im Code festlegen, in welches File geloggt werden soll.

Log4net kenne ich schon, jedoch scheint mir das hier nicht geeignet (Overkill). Ich möchte ja auch explizit jeweils einen Logger verwenden, also sagen: "Log nach File xy" oder "Logge in die Console". Bei log4net ist es ja eher so dass man nach Log-Level logt und je nachdem was definiert wurde, logt es dann in die jeweiligen Appender (Logger).. ich müsste also log4net ganz schön umbiegen um auf das benötigte Verhalten zu kommen.

Kennt da jemand was, das auf meine Anforderungen passt (und am besten noch kostenlos ist... 😃 ).

Danke und Gruss, Shi

14.07.2009 - 11:30 Uhr

ok, also nochmal für die langsamen (mich) 😃 , kann ich das mal so bestätigt haben (damit ich beruhigt schlafen kann):

das heisst also ich mache z.b. eine Klasse "UserController" in "BusinessLogic", also meiner Geschäftslogik-Schicht, dort habe ich z.b. irgendwelche User-Spezifischen Logiken/Berechnungen etc., welche von der View/GUI konsumiert werden. Dieser UserController selber greift wenn nötig auf den DataAccess zu (wo das Entity Framework, die Entities, der Context liegen).

Eine solche Methode:

public void List<User> GetAllDeletedUsers()
{
	using (AnwendungXyEntities context = new (AnwendungXyEntities())
	{
		var users =	from u in context.Users
				where u.Deleted
				select u;
	
		return users.ToList();
	}
}

Wäre also auch in dieser Klasse ("UserController") zu finden, also in der BL?

Mir ist natürlich bewusst das viele Wege nach Rom führen, aber mir geht es hier um grundlegende Designfehler. Btw, falls sich jemand das fragt: ja, ich möchte direkt die Entities als "BussinesObjects" verwenden.

13.07.2009 - 17:40 Uhr

Naja, bin mir nicht sicher ob du jetzt einfach den Teil weggelassen hast.

Aber im Prinzip ist das erst ne Select-Abfrage. Hier werden keinerlei Änderungen in die DB zurückgeschrieben. Du erstellst lediglich mit dem "new" ein neues Objekt welches du dann verwenden könntest, aber eine Änderung bewirkt das nicht.

13.07.2009 - 16:05 Uhr

Das Buch scheint recht gut zu sein, anhand der Beschreibung und den Kommentaren. Ich glaube ich werde mir das zulegen.

Ich bin auf der Suche noch auf diesen Artikel gestossen:
http://blogs.objectsharp.com/CS/blogs/barry/archive/2008/05/06/the-entity-framework-vs-the-data-access-layer-part-1-the-ef-as-a-dal.aspx

Was mir noch nicht ganz klar ist.. ich möchte ja meine Applikation ein wenig gescheit strukturieren. Es gibt zwei Clients. Einmal ein WinForm-Client und ein ASP.NET-Client, dann dachte ich, ich mache in der Solution jeweils Subprojekt (welches ich dann referenziere) "BussinessLogic" und "DataAccess".

Im DataAccess wird das *.edmx-Erstellt und evtl. eigene Extension-Methoden.

Im BusinessLogic sollten dann so generelle Logiken rein wie, "überprüfe Benutzer Login", "Berechne xy" oder auch sowas im sinn von "Lösche Benutzer", wobei dann mehrere Methoden des DataAccess verwendet werden, so dass eine Art ablauft abgebildet wird (User kann nur gelöscht werden wenn keine offenen Aufträge, etc. wenn alles ok -> markieren als löschen).

Ich hoffe bis hierhin sind meine Überlegungen schonmal nicht falsch?

Ich bin mir nicht sicher wo ich solche Methoden platzieren soll:GetAllUsers();
GetUserById(int id);
GetAllCategories();
DeleteUserById(int id);
CreateUser(user userEntity);

Ich dachte bisher die gehören in den DataAccess, aber vermutlich platziert man sowas in die "BusinessLogic" in eine Klasse z.B. UserHandler/UserController?

Shi

13.07.2009 - 13:32 Uhr

Kennst du eine Seite, welche diesen Aufbau und die Verwendung des Entity Frameworks gut beschreibt? Meistens finde ich nur Tutorials die einem einzelnen Blog-Post entsprechen, und die gehen dann nur auf die ganzen Basics ein. Aber bezüglich der Struktur/Architektur (wo platziere ich was) habe ich noch nichts schlaues gefunden.

13.07.2009 - 11:14 Uhr

SaveChanges wird schon nur im DataAccessHandler verwendet. Aber ich denke es gibt in der Tat noch Verbesserungen bezüglich der Architektur die ich vornehmen kann.

Wie würdest du das umsetzen, in dieser Klasse (DataAccessHandler). Würdest du die Member-Variable "context" also entfernen und stattdessen in den einzelnen Methoden jeweils ein:

using (XyEntities context = new XyEntities())
{
    var mn = ....;
}

verwenden?

13.07.2009 - 10:21 Uhr

@LaTino:

Ok, ich probiere das mal so aus, wie du das beschrieben hast, danke.

Btw, wegen dem Context. Unser ASP.NET-Dozent hatte uns das mal so gezeigt. das wir das etwas so machen sollen:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DataAccess
{
    /// <summary>
    /// Handle data access
    /// </summary>
    public class DataAccessHandler : IDisposable
    {

        #region Fields

        /// <summary>
        /// The context (Entity Model)
        /// </summary>
        MediathekEntities context = new MediathekEntities();

        #endregion
		
		...		

        /// <summary>
        /// Get a list of all avaiable titles a customer can have.
        /// (Ordered by description)
        /// </summary>
        /// <returns>List with titles</returns>
        public IList<Title> GetTitles()
        {
            var titles = from t in this.context.Titles
                         orderby t.Description
                         select t;

            return titles.ToList();
        }

        /// <summary>
        /// Get a title by a given title ID.
        /// </summary>
        /// <returns>The title or null when nothing found</returns>
        public Title GetTitleById(int titleId)
        {
            var title = from t in this.context.Titles
                         where t.TitleId == titleId
                         select t;

            return (Title)title.First();
        }
		
		...

        #region IDisposable Members

        /// <summary>
        /// Dipose this object
        /// </summary>
        public void Dispose()
        {
            if (context != null)
            {
                context.Dispose();
            }
        }

        #endregion
    }
}

Und dann können wir das natürlich in einem "Using" aufrufen, damit der context wieder sauber entfernt wird:

        /// <summary>
        /// Delete an administrator account
        /// </summary>
        /// <param name="adminId">The ID of the administrator account</param>
        public void DeleteAdministrator(int adminId)
        {
            using (DataAccessHandler da = new DataAccessHandler())
            {
                da.DeleteAdministratorById(adminId);
            }
        }

Aber ich glaube ich sehe schon was du meinst.. macht ja irgendwie keinen Sinn.. man kann den Using+Context direkt in diese Methode packen, anstelle das mit dem DataAccessHandler anzustellen.

12.07.2009 - 11:13 Uhr

Danke, aber es geht hier nicht über das ComboBoxBinding.

Wie auch immer, soweit ich bis jetzt gelesen habe, ist das ein Design-Flaw im Entity Framework 1.0, es gibt aber einige Workarrounds:

http://bernhardelbl.spaces.live.com/blog/cns!DB54AE2C5D84DB78!238.entry

Ich hoffe das ist in der Version 4.0, (VS2010) dann besser gelöst).

12.07.2009 - 00:57 Uhr

Erm.. sorry, aber hast du meinen Text durchgelesen?

A) die Zuweisung ist nicht das Problem
B) Du castest hier einen Value (der wohl meistens Integer ist) zu einem Entity
C) Irgendwie habe ich das gefühl, wenn ich auch auf die Uhrzeit schaue, du bist betrunken 😃

11.07.2009 - 14:29 Uhr

verwendetes Datenbanksystem: MSSQl 2008

Hallo,

Ich versuche mich mal wieder mit dem Enitity Framework. Normale Selects und Updates klappen bisherer ohne grössere Probleme. Auch das Erstellen und Einfügen von neuen Objekten in die DB hatte bisher geklappt. Nun habe ich aber den Fall, dass bei einem Enitity eine Relation zu einem anderen Objekt/Tabelle besteht und beim Einfügen verursacht mir das nun grösste Probleme.

Ich habe eine Entity "User", ein User kann eine Anrede haben, diese Anreden sind in der Tabelle "Titles", es gibt also ein Entity "Title". (Die übersicht über diese Entitys habe ich als Bild angehängt).

Beim erstellen eines neuen Entities des Typs User gehe ich bis jetzt wie folgt vor:

  • Ich erstelle mir ein Objekt User:
 User user = new User();

Danach fülle ich das Objekte mit den gewünschten Werten:

user.City = this.txtCity.Text;
user.CountryIso = this.txtCountry.Text;
user.Email = this.txtEmail.Text;
user.Firstname = this.txtFirstname.Text;
user.Password = this.txtPassword.Text;
user.Street = this.txtStreet.Text;
user..Surname = this.txtSurname.Text;
user.Zip = this.txtZip.Text;

Dann ermittle ich welche Anrede der User hat, und füge sie dem User hinzu:

 user.Title = da.GetTitleById(((GenericIntStringItem)cbTitles.SelectedItem).IntVal);

Die Methode ist wie folgt implementiert:

 /// <summary>
/// Get a title by a given title ID.
/// </summary>
/// <returns>The title or null when nothing found</returns>
public Title GetTitleById(int titleId)
{
var title = from t in this.context.Titles
where t.TitleId == titleId
select t;

Title titleEntity = (Title)title.First();

// detach from context
this.context.Detach(titleEntity);

return titleEntity;
}

Hier muss ich anmerken, dass ich zuerst das "Title" objekt nicht vom Objekt context "detached" hatte, dann wurde aber speichern des Users gemekert, dass das Objekt bereits einem anderen Context zugeordnet war (damit war "Title" gemeint). Ob das hier eine schlaue Lösung ist weiss ich nicht... jedenfalls:

Versuche ich noch den User wie folgt zu speichern:

 /// <summary>
/// Creates a given user account
/// </summary>
/// <param name="user">The customer that shall be inserted into the DB</param>
public int CreateUser(User user)
{
this.context.AddToUsers(user);
this.context.SaveChanges();

return user.UserId;
}

Das schmeisst mir aber eine Exception (aber nur wenn ich dem User einen Title zugeordnet habe, wenn Title == null, dann geht das speichern):

{System.InvalidOperationException: The object cannot be added to the ObjectStateManager because it already has an EntityKey. 

Klar, mir ist das schon irgendwie logisch, der Title existiert ja schon, hat ja schon eine TitleId, ich möche ja aber nicht einen neuen Title hinzufügen sondern nur den User beim erstellen gleichzeitig mit einem bestehenden Title verknüpfen. Wenn ich in der CreateUser-Methode vor "AddToUser" zuerst den Title Attache, dann klappt das auch nicht:

 /// <summary>
/// Creates a given user account
/// </summary>
/// <param name="user">The customer that shall be inserted into the DB</param>
public int CreateUser(User user)
{
if (user.Title != null)
{
this.context.Attach(user.Title);
}
this.context.AddToUsers(user);
this.context.SaveChanges();

return user.UserId;
}

Dann meldet er mir:

"An object with the same key already exists in the ObjectStateManager. The existing object is in the Unchanged state. An object can only be added to the ObjectStateManager again if it is in the added state."

Weiss einer was ich hier falsch mache? Oder ist generell das Vorgehen falsch und ich sollte beim User-Entity einfach das Property für "TitleId" einfügen und dann dieses mit der gewünschten Id befüllen vor dem speichern und nicht über diese Relationen arbeiten?

Gruss, Shi

09.07.2009 - 14:52 Uhr

verwendetes Datenbanksystem: MSSQL 2008

Hallo zusammen

Ich habe bisher mit typisierten DataSets gearbeitet, nun arbeite ich mich so langsam in das Thema ADO.NET Entity Framework ein. Dabei möchte ich eigentlich die Linq to Objects oder Object Services verwenden (oder wie das auch immer heisst 😉 ).

Im GUI setze ich Windows-Forms ein (nicht WPF). Was ich nun gerne wissen möchte ist, wie ich am schlauesten eine ComboBox mit Werten aus einem Enitity (resp. einer Liste von Entities fülle).

Klar, ich kann ja DatenBindung verwenden. Jedoch möchte ich, A) dass die Werte in der ComboBox alphabetisch sortiert sind, B) zusätzlich in die ComboBox ein Element einfüllen im Sinne von "<Bitte wählen>". Also ein nicht gebundenes Element, welches dem User dann verdeutlicht, dass er eine Selektion durchführen soll. Also fällt wohl Daten-Bindung weg.

Wie gehe ich nun aber vor, etwa so ? :

  • Erstellen einer Klasse, welche zwei Felder hat. Eines für Integer (für den Value des gewählten Objektes, also der ID), und ein String-Feld (für den angezeigten Text in der Combobox).
    Zusätzlich braucht die Klasse noch ein CompareTo, damit ich die Objekte in einer Liste Sortieren kann.
    Ausserdem muss ich noch ToString() überschreiben, damit in der Combobox auch was sinnvolles angezeigt wird (der Inhalt des String-Feldes).
  • Dann, in meinem Form mache ich eine Methode um z.B. eine ComoBox mit Anreden zu füllen:
/// <summary>
        /// Fill title combobox with titles
        /// </summary>
        private void FillTitles()
        {
            // cleanup
            this.cbTitles.Items.Clear();

            foreach (GenericIntStringItem item in bl.GetTitleListForComboBox())
            {
                this.cbTitles.Items.Add(item);
            }

            // select default entry
            if (this.cbTitles.Items.Count > 0)
            {
                this.cbTitles.Text = Constants.ComboBoxDefaultItemText;
            }
        }

-> Alte Items löschen. GetTitleListForComboBox() holt mir eine Liste mit Objekten, welche wie oben beschrieben ein Int und ein String Feld haben. So kann ich später direkt wieder die ID des Objektes, welches ja die TitleId in der Datenbank-Tabelle darstellt ermitteln und verwenden.

Ist das so schlau gelöst, oder begehe ich hier gravierenste Design-Fails?

Gruss, Shi

16.06.2009 - 09:40 Uhr

also ich konnte es mittlerweile lösen. Das mit den einzelnen Werten wäre sonst sicher auch ok gewesen :

/// <summary>
        /// Fills the filter DataGridView
        /// </summary>
        protected void FillFilterGrid()
        {
            // cleanup
            dgvFilter.Rows.Clear();
            dgvFilter.Columns.Clear();

            // create and add the columns to the grid
            DataGridViewColumn dgvTgtClmDescription = new DataGridViewTextBoxColumn();
            DataGridViewColumn dgvTgtClmDataProperty = new DataGridViewTextBoxColumn();
            DataGridViewColumn dgvTgtClmValue = new DataGridViewTextBoxColumn();

            // name/header text
            dgvTgtClmDescription.HeaderText = dgvTgtClmDescription.Name =
                Res.DgvFilterClmHeaderDescription;
            dgvTgtClmDataProperty.HeaderText = dgvTgtClmDataProperty.Name =
                Res.DgvFilterClmHeaderDataProperty;
            dgvTgtClmValue.HeaderText = dgvTgtClmValue.Name =
                Res.DgvFilterClmHeaderValue;

            // set value types
            dgvTgtClmDescription.ValueType = typeof(string);
            dgvTgtClmDataProperty.ValueType = typeof(string);
            dgvTgtClmValue.ValueType = typeof(string);

            // read access
            dgvTgtClmDescription.ReadOnly = true;
            dgvTgtClmDataProperty.ReadOnly = true;

            // visibility
            dgvTgtClmDataProperty.Visible = false;

            // add to dgv
            dgvFilter.Columns.Add(dgvTgtClmDescription);
            dgvFilter.Columns.Add(dgvTgtClmDataProperty);
            dgvFilter.Columns.Add(dgvTgtClmValue);

            // add rows according to the current datagridview which is displayed
            if (dgv != null)
            {
                int rowIndex = 0;

                foreach (DataGridViewColumn dgvClm in dgv.Columns)
                {
                    if (dgvClm != null && dgvClm.Visible)
                    {
                        DataGridViewRow newDgvRow = new DataGridViewRow();

                        // add row to filter grid
                        dgvFilter.Rows.Insert(rowIndex, newDgvRow);

                        // prepare "description" column
                        dgvFilter[dgvTgtClmDescription.Name, rowIndex].Value =
                            dgvClm.HeaderText;

                        // prepare "data property" column
                        dgvFilter[dgvTgtClmDataProperty.Name, rowIndex].Value =
                            dgvClm.DataPropertyName;

                        rowIndex++; // prepare next row index
                    }
                }
            }
        }

Irgendwie hing es mit dem RowIndex ab der hinzugefügten Zeile, die erste hinzugefügte Zeile hatte nen korrekten Index, die zweite bekam jedoch "-1", dass veranlasst die Exception. Ich habe nun "Insert" statt "add" genommen um selber zu bestimmen, welchen RowIndex die neue Zeile erhält.

15.06.2009 - 16:39 Uhr

Hallo, ich kenne mich mit dem "Fast-Object-Server" zwar nicht aus, jedoch gehe ich davon aus, dass du wohl kaum direkt die "physikalischen" Dateien des Servers verwenden sollst.

Musst du nicht eher via z.B. ODBC (mit entsprechendem Treiber) eine Verbindung zum DB-Server aufbauen? Dann kannst du deine Queries absetzen um an die gewünschten Daten zu kommen.

15.06.2009 - 16:18 Uhr

Hallo,

Hört sich zwar nach ner einfachen Frage an, aber ich bin langsam am verzweifeln.

Ich habe ein ungebundenes DataGridView und habe manuell per Code die einzelnen Spalten definiert und hinzugefügt (jeweils "DataGridViewTextBoxColumn").

Nun würde ich gerne rows zum Grid hinzufügen, weiss aber nicht genau was der schlauste weg ist.

Mit:

DataGridViewRow newRow = new DataGridViewRow();

kann ich eine neue Zeile festlegen und ja auch nachher via

dataGridView.Rows.Add(newRow);

hinzufügen.

Leider schmeisst er mir nun beim abfüllen der Zellen eine Exception:
"System.ArgumentOutOfRangeException"

Aber ich kann mir das nicht erklären. Der ValueType der Column wurde von mir extra manuell auf "String" festgelegt und der Wert, den ich hinzufügen will ist ein "String". Merkwürdig ist auch, dass das Abfüllen der ersten Row funktioniert, bei der zweiten kommt aber ein Fehler (ein anderer String, aber nichts "aussergewöhnliches".

Evtl. hilft euch der komplette Code besser zum Verständnis:

        protected void FillFilterGrid()
        {
            // cleanup
            dgvFilter.Rows.Clear();
            dgvFilter.Columns.Clear();

            // create and add the columns to the grid
            DataGridViewColumn dgvTgtClmDescription = new DataGridViewTextBoxColumn();
            DataGridViewColumn dgvTgtClmDataProperty = new DataGridViewTextBoxColumn();
            DataGridViewColumn dgvTgtClmValue = new DataGridViewTextBoxColumn();

            // name/header text
            dgvTgtClmDescription.HeaderText = dgvTgtClmDescription.Name =
                Res.DgvFilterClmHeaderDescription;
            dgvTgtClmDataProperty.HeaderText = dgvTgtClmDataProperty.Name =
                Res.DgvFilterClmHeaderDataProperty;
            dgvTgtClmValue.HeaderText = dgvTgtClmValue.Name =
                Res.DgvFilterClmHeaderValue;

            // set value types
            dgvTgtClmDescription.ValueType = typeof(string);
            dgvTgtClmDataProperty.ValueType = typeof(string);
            dgvTgtClmValue.ValueType = typeof(string);

            // read access
            dgvTgtClmDescription.ReadOnly = true;
            dgvTgtClmDataProperty.ReadOnly = true;

            // visibility
            dgvTgtClmDataProperty.Visible = false;

            // add to dgv
            dgvFilter.Columns.Add(dgvTgtClmDescription);
            dgvFilter.Columns.Add(dgvTgtClmDataProperty);
            dgvFilter.Columns.Add(dgvTgtClmValue);

            // add rows according to the current datagridview which is displayed
            if (dgv != null)
            {
                foreach (DataGridViewColumn dgvClm in dgv.Columns)
                {
                    if (dgvClm != null && dgvClm.Visible)
                    {
                        DataGridViewRow newDgvRow = new DataGridViewRow();

                        // add row to filter grid
                        dgvFilter.Rows.Add(newDgvRow);

                        // prepare "description" column
                        newDgvRow.Cells[dgvTgtClmDescription.Name].Value =
                            dgvClm.HeaderText;

                        // prepare "data property" column
                        newDgvRow.Cells[dgvTgtClmDataProperty.Name].Value =
                            dgvClm.DataPropertyName;
                    }
                }
            }
        }

Im unteren Foreach wird die neue Zeile erstellt, hinzugefügt und dann gefüllt. Bei dieser Codestelle, krieg ich beim zweiten durchgang die Exception:

                        // prepare "description" column
                        newDgvRow.Cells[dgvTgtClmDescription.Name].Value =
                            dgvClm.HeaderText;

Hat jemand ne Idee, warum das Argument nicht geschluckt wird?

Danke und Gruss, Shi

Edit:
Wie ich gerade merke, wenn ich auf das Value Property zugreiffen möchte (beim zweiten Durchgang), also z.B. im Code oder mit der Schnellüberwachung:

newDgvRow.Cells[dgvTgtClmDescription.Name].Value

Dann wirft es mir schon die gleiche Exception "Argument out of range"... WTF? Der soll mir ja nur das "Object" mal zurückgeben.

21.05.2009 - 18:17 Uhr

Ok, danke für den Hinweis, du hast recht, da kann man natürlich noch etliche Spalten in der Haupttabelle übernehmen.

Ich habe aber gleich noch eine Änderung gemacht bezüglich des PK/FK. Der PK von "media" ist nun zugleich auch der PK innerhalb der spezifischen Tabellen und via FK mit dem Haupt-PK verknüpft.

Idee gemäss folgendem PDF: http://kilauea.zhaw.ch/MSE-EC/material/050_ApplicationServer.pdf (PDF Seite 64). (und jaaa.. es ist Java 😉 )

Anbei noch ein aktualisiertes DB-Diagram (ist aber immer noch Work in Progress).

20.05.2009 - 23:54 Uhr

Hallo zusammen,

Ich versuche eine kleine Anwendung zu entwicklen, welche z.B. in Bibliotheken eingesetzt werden kann um die Medien zu verwalten und deren Status (ausgeliehen/verfügbar etc.). Dazu möchte ich auch das ADO Enitity Framework verwenden (was für mich aber noch eher Neuland ist (habe früher aber schonmal hibernate eingesetzt).

Es soll für die User auch möglich sein, wie einer Webseite die Medien zu reservieren. Nun steh ich aber bezüglich des Db-Designs etwas auf dem Schlauch, weil es unterschiedliche Medientypen gibt.

Normalerweise gibt es ja z.B: Bücher, DVDs, CD, teilweise dann auch Spiele etc. Wenn ich nun also eine Tabelle für die Reservierungen mache, mit den Spalten:

Reservierungs_ID, User_ID, Media_Id

Dann könnte ich ja eine Medientabelle machen, welche nur als Haupttabelle "Media" dient, damit ich eindeutige Medien-Ids habe. Ich möchte in der Reservierungstabelle ja nicht sowas haben wie:

Reservierungs_ID, User_ID, Media_DVD_Id, Media_CD_Id

Nun müsste ich aber für die Medientypen eigene Tabellen definieren, in denen auch je nach Typ andere Spalten drin sind. Für Bücher z.B: "Autor", "ISBN". Für DVDs z.B. "Regisseur", "Filmlänge". In dieser Tabelle würde ich dann einen Verweis haben auf die "HauptId" (in der Tabelle "Media"). Z.B.:

media_dvd_id, titel, description, filmlaenge, regisseur, ...

Ist das die ideale Lösung? Macht man das so? 😃 Ich meine ich komme so schon an alle Daten, aber evtl. gibt es einen besseren Weg. Irgendwie sehe ich einfach in Folgendem ein Problem. Mit dem Entity Framework werden ja die Relations zwischen den Tabellen automatisch übernommen (was nützlich ist). Wenn ich aber nun von meiner Haupt-Medientabelle "Media" zum reelen Medium will (also Buch, DVD, was auch immer), dann kann ich von dort aus nicht direkt hin navigieren. Ich habe ja die Verknüpfung wie dem Umgekehrten Weg, also nur von der Tabelle Media_DVD zu Media, ich denke das ist unschön, oder nicht?

Anbei seht ihr noch das DB-Design etwas verständlicher dargestellt in einem Bild.

Wie seht ihr das?

Gruss, Shi

15.05.2009 - 15:46 Uhr

Ich weiss nicht, das scheint mir ehrlichgesagt ein zu komplexer Barcode zu sein.

Ok, im Moment geht es mir ja eh nur darum, dass ich den Barcode lesen möchte und nicht selbst generiere. Denoch möchte ich nicht, dass für die Software zu "gigantische" Barcodes verwendet werden.

Im Prinzip möchte ich ja eine Art fortlaufende Nummer verwenden können, ohne Prüfziffer, also z.b.:

1000001 -> Buch xy
1000002 -> DVD Mn
1000003 -> Buch Ij

Auf dem Etikett soll dann ja auch die Nummer lesbar ersichtlich sein, so dass ein Angestellter diese ablesen könnte. Darum sollte sie auch nicht zu lange sein.

Ich möchte ja den Code nicht zusammensetzen wie bei einem EAN, wo da vielleicht die Lagerposition, der Artikel, der Preis etc. drin kodiert ist, sondern nur eine durchnummerierung. Dann kann man schön bei nem Printshop oder so ne Rolle Barcodes Xy Bestellen mit der gewünschten Start- und End-Zahl. (und später evtl. im Programm selbst erstellen, wenn ich Zeit habe, was kaum der Fall sein wird).

15.05.2009 - 14:51 Uhr

Hallo zusammen,

Ich wollte eine Art Anwendung machen für die Verwaltung einer Mediathek. Also Bücher, DVDs, CDs etc.

Jeder Artikel müsse ja eindeutig identifiziert werden können. Hilfreich wäre es ausserdem wenn die Kennzeichnung elektronisch eingelesen weren könnte, also ist ein Barcode zu verwenden wohl naheliegend.

Nur was für einen? Ich habe mich im Wikipedia mal durch den Barcode-Artikel gelesen und durch die wichtigsten Codes die es so gibt, jedoch habe ich etwas Mühe herauszufinden, welcher wohl hier am geeignetsten eingesetzt wird, auch so dass es keinen Overhead gibt. Der Barcode soll ja eigentlich nur zur Inventarisierung dienen, also nur eine Art "ArtikelId" speichern die eindeutig ist und sonst keine weiteren Infos, das wird ja dann über den Verknüpften Artikel zur Verfügung gestellt.

Btw, ich habe nicht vor in der Anwendung das Ausdrucken des Codes einzubauen (evtl. später schon). Aber man kann ja solche Klebeetiketten auch bestellen, welche man dann auf die Bücher/DVDs pappen könnte.

Code128 scheint mir ein geeigneter Kandidat zu sein, da man den Wert kurz halten könnte (anders als. zB. EAN13, so viele Stellen brauche ich ja nicht unbedingt).

Mir wäre es einfach wichtig, dass der Wert nicht zu lang und kryptisch wäre, weil ich möchte dass man diese auch notfalls schnell von Hand eintippen könnte.

Jemand ne Idee, resp. Erfahrung mit sowas?

Gruss, Shi

13.05.2009 - 13:51 Uhr

Hallo,

Ich habe eine Konsolenanwendung welche gelegentlich Abstürzt und dabei den Dialog "Fehlerbericht senden" zeigt. Der "normale" Exceptiondialog erscheint leider nicht.

Ich habe nun mal in die Anwendung eingebaut, dass unbehandelte Exceptions in einer speziellen Methode behandelt werden, um dem Fehler auf die Schliche zu kommen:

            AppDomain.CurrentDomain.UnhandledException +=
                new UnhandledExceptionEventHandler(UnhandledExceptionHandler);

Ich bin mir aber nun nicht sicher, ob ich nicht auch noch Thread-Exceptions "fangen" muss. Soweit ich bisher immer gelesen habe, gilt das scheinbar nur für Windows-GUI-Anwendungen?

Kann ich sowas in Konsolenanwendungen also getrost weglassen?

Application.ThreadException += 
   new System.Threading.ThreadExceptionEventHandler(
   Program.Application_ThreadException);

Btw: ich verwende in der Konsolenanwendung Threading. Gewisse Prozesse sind in eigene Threads ausgelagert.

Danke und Gruss, Shi

05.05.2009 - 15:44 Uhr

Bei Trigger sehe ich aber ein Problem (es sei denn ich blicke das was nicht):

Ich möchte ja die User_Id mitspeichern, welche die Änderung vollzogen hat. Wenn nun nach einem "INSERT" z.B. der Trigger ausgelöst wird und mir in eine Tabelle QueryTable z.B. die USER_ID, das aktuelle Datum und den Querytext reinschreiben soll, dann habe ich die USER_ID des Verursachers gar nicht, sondern nur die des betroffenen Datensatzes. Die des Verursachers wird ja primär gar nicht verwendet.

05.05.2009 - 15:33 Uhr

ja das wäre vermutlich der Königsweg. Leider fehlen mir im Moment die Resourcen sowas sauber durchzuplanen und dann zu implementieren, deswegen dachte ich ob es evtl. eine simplere Lösung gäbe in dem ich einfach das ausgeführte Query logge, was mir erstmal genügen würde.

05.05.2009 - 15:26 Uhr

Ja, ich kenne den Profiler und setze ihn auch ein, aber für dieses Szenario ist er ungeeignet.

Sagen wir, wir haben einen Benutzermanager und gewisse Rechteanpassungen sollen geloggt werden. Wer hat wann welchem User was hinzugefügt. Ich möchte nicht 24/7 den Profiler laufen lassen um an ein Tracefile zu kommen.

05.05.2009 - 15:11 Uhr

Hallo,

Mich würde interessieren, ob ihr eine Möglichkeit kennt, wie man an ein abgesetztes Query rankommt, mitsammt Parametern (bei typisierten DataSets).

Wenn ich also im Code z.B. wie folgt ein Query absetze:

userTableAdapter.Insert(userId, userName, userPwd);

Dann wäre das Query ja zum Beispiel:

INSERT INTO user (USER_ID, NAME, PWD) VALUES (100, "petermuster", "zensursula");

resp. bei typisierten DataSets im eher im Stil von:

INSERT INTO user(USER_ID, NAME, PWD) VALUES(@userId, @name, @pwd); DECLARE @userId int ..... SET @userId = 100' ....

Jemand ne Idee? Bei dem Tableadapter "userTableAdapter" z.B. finde ich nichts, evtl. gibt es sowas leider auch gar nicht?

Wir würden nämlich gerne ein paar ausgewählte, wichtige Queries loggen, damit nachvollzogen werden kann wer was genau gemacht hat.

Gruss, Shi

17.04.2009 - 11:15 Uhr

Ok, danke für die Hinweise. Klingt alles einleuchtend.

16.04.2009 - 09:50 Uhr

Hallo zusammen,

Beim rumstörbern im Code habe ich mir mal die automatisch generierte Klasse angeschaut, welche Visual Studio anlegt, wenn ich mir eine String-Resourcen-Datei erzeuge. Dabei bin ich auf folgenden Code gestossen, welche den ResourcenManager zurückgibt:

        private static global::System.Resources.ResourceManager resourceMan;
        
        /// <summary>
        ///   Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird.
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute (global::System.ComponentModel.EditorBrowsableState.Advanced)]
        internal static global::System.Resources.ResourceManager ResourceManager {
            get {
                if (object.ReferenceEquals(resourceMan, null)) {
                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ClientXy.Resources.Strings", typeof(Strings).Assembly);
                    resourceMan = temp;
                }
                return resourceMan;
            }
        }

Falls dieser noch nicht vorhanden ist, wird er also zuerst erzeugt. Kann mir einer sagen, welchen Sinn es macht zuerst hier das "temp" Objekt zu kreieren? Man hätte doch genausogut direkt folgendes machen können:

if (object.ReferenceEquals(resourceMan, null)) {
	resourceMan = new global::System.Resources.ResourceManager("ClientXy.Resources.Strings", typeof(Strings).Assembly);
}
return resourceMan;

Oder sehe ich da was falsch? Ausserdem...
Warum:

if (object.ReferenceEquals(resourceMan, null))

Wenn man doch folgendes machen könnte:

if (resourceMan == null)

Vielleicht kann ja einer hier etwas licht ins Dunkel bringen 😃

Gruss, Shi

07.04.2009 - 12:41 Uhr

Problem hat sich erledigt, habe den Fehler gefunden. Lag wirklich woanders - is aber zu peinlich um hier genauer zu erörtern 😉 wegrenn

07.04.2009 - 11:27 Uhr

Ja, "so halb" würde ich sagen. 😉

Ich arbeite schon mit dem FrmChild, jedoch wird dieses wie folgt instanziert:

FrmParent frmChild = null;

switch(DesiredType)
{
     case DesiredType.ChildAbc:
          frmChild = new FrmChild();
          break;
     case DesiredType.ChildDef:
          frmChild = new FrmChildDef();
          break;
}

Ich weiss, dass das somit Auswirkungen hat, da ich es ja als "FrmParent" deklariere. Denoch dachte ich, dass es mit dem Override dann doch die Methode des Childs ausführen würde.

07.04.2009 - 11:13 Uhr

Hallo,

Ich habe im Moment Mühe bei Events und vererbten Forms. Folgendes Szenario:

Formulare: FrmRandom, FrmParent, FrmChild -> wobei also FrmChild:FrmParent

Nun ist es so dass ich im FrmParent einen Event überwache und abhandle:

FrmRandom.EventXy += OnEventXy;

Soweit so gut, der Event wird ausgelöst und mit der Methode "OnEventXy" der Klasse FrmParent "abgearbeitet".

Nun habe ich aber noch ein Kind-Form "FrmChild", welches einerseits diese Abarbeitung der FrmParent-Klasse erledigen soll, zusätzlich aber noch weitere Aktionen durchführen sollte.

Soweit ich bereits herausgefunden habe, funktioniert es nicht, wenn ich die Methode "OnEventXy" in der Parent-Klasse als virtual deklariere und in der Child-Klasse mit override überschreibe -> es wird trotzdem nur die Methode im FrmParent ausgeführt.

In einem anderen Thread las ich etwas über das Template-Pattern, welches ich umsetzen wollte, bin mir aber nicht sicher ob ich es richtig verstanden habe:

Ich habe "OnEventXy" umgebaut, damit die eigentliche Abarbeitung ausgelagert wird:

In FrmParent:

private void OnEventXy()
{
     HandleEventXy();
}

protected virtual void HandleEventXy()
{
     // do base stuff
     // ...
}

In FrmChild, wird dass dann überschrieben:

protected override void HandleEventXy()
{
     base.HandleEventyXy(); // do base stuff
     
     // perform specific tasks
     // { ... }
}

Das hat aber leider nichts gebracht, es wird nur die Methode in FrmParent aufgerufen. Mir scheint ich habe den Code nur umgelagert und das Verhalten dabei nicht verändert.

Was mache ich falsch, resp. wie packt man dieses Problem richtig an?

Danke und Gruss, Shi

31.03.2009 - 16:36 Uhr

Das habe ich schon mal hier im Forum gehört und ich habe mal selbst die Erfahrung gemacht, dass es Probleme geben kann. Das war im Zusammenhang mit Threading, da wurde eine Connection versucht zu verwenden obwohl diese woanders noch gebraucht wurde ("Diesem Befehl ist bereits ein geöffneter DataReader zugeordnet..." war glaubs der Fehler).

Ich lege nun jeweils vor dem benutzen einer Connection eine Kopie der "Hauptconnection" temporär an:

SqlConnection connection = new SqlConnection(mainConnection.ConnectionString);
// Verbindung korrigieren
typedTableAdapter.Connection = connection;

Haltet ihr dieses Vorgehen für korrekt oder gibt es da eine bessere Lösung?

02.03.2009 - 11:22 Uhr

Danke, damit klappt's.

Ist ja auch sehr simpel, man muss Microsoft wirklich loben, Klasse gemacht.

02.03.2009 - 10:37 Uhr

Hallo,

In unserer Software werden Anwendungs- und Benutzer-Einstellungen in der "Anwendungs-Einstellungsdatei" gespeichert/definiert. Das heisst, Benutzer-Einstellungen werden, wenn sie abweichen im Benutzerprofil gespeichert.

Nun ist es jedoch so dass wir relativ häufig eine neue Version der Software ausliefern. Bis jetzt ist es so, dass die Benutzer ihre persönliche Einstellungen alle verlieren, da diese Benutzereinstellungen je nach Version in einem anderen Ordner gespeichert werden (im Userprofil).

Das ist natürlich nicht so toll. Kenn ihr da eine Ablösung? Gibt es irgendeinen weg, um die alten Benutzereinstellungen auf einfache Art und weise in das neue File zu kopieren?

Danke und Gruss, Shi