Laden...
Avatar #avatar-2880.jpg
Florian Reischl myCSharp.de - Experte
Softwarearchitekt, Technischer Projektleiter München Dabei seit 16.10.2007 1.564 Beiträge
Benutzerbeschreibung

Forenbeiträge von Florian Reischl Ingesamt 1.564 Beiträge

09.12.2009 - 13:52 Uhr

Hi

Ich habe die Sache jetzt ein ganzes Stück vereinfacht. Dabei kopiere ich die IDs einmal, aber das sollte kein Performance-Problem darstellen.

Kurz zum Konzept:
Als erstes wird die FID aus der Datenquelle in eine Temp-Table (@ids) geholt. Dabei gleichzeitig alle IDs für die Tabelle1, 2 und 3 erzeugt. Danach können die Werte immer auf einmal in die Zieltabellen eingefügt werden. Die vorher erzeugten IDs werden einfach über einen JOIN zur Verfügung gestellt.

Das mit der UIProperty habe ich noch nicht ganz gerafft 😃 . Wenn du mir verrätst aus welcher Tabelle die kommt und welche Kriterien gelten um diese zu ermitteln kann ich das noch mit reinbacken.

Sollte jetzt ein ganzes Stück leichter zu verstehen sein:

---=================================================================
-- setup
DECLARE @data TABLE (
   FID UNIQUEIDENTIFIER
   ,ChangeDate DATETIME
   ,BinaryValue IMAGE
   ,ControlName VARCHAR(100)
);
DECLARE @table1 TABLE (
   Id UNIQUEIDENTIFIER
   ,StreamValue IMAGE
);
DECLARE @table2 TABLE (
   ID UNIQUEIDENTIFIER
   ,HistoryId UNIQUEIDENTIFIER
   ,UIProperty VARCHAR(100)
);
DECLARE @table3 TABLE (
   ParentObject UNIQUEIDENTIFIER
   ,Property UNIQUEIDENTIFIER
)

INSERT INTO @data
             SELECT NEWID(), GETDATE(), 0x1, 'ctrl1'
   UNION ALL SELECT NEWID(), GETDATE(), 0x2, 'ctrl2'
   ;

---=================================================================
-- sample
DECLARE @ids TABLE (
   FID UNIQUEIDENTIFIER
      PRIMARY KEY CLUSTERED
   ,T1Id UNIQUEIDENTIFIER
   ,T2Id UNIQUEIDENTIFIER
   ,T3Id UNIQUEIDENTIFIER
);

--------------------------------------------------------------------
-- get FID values to fill the anchors table
INSERT INTO @ids
   SELECT FID, NEWID(), NEWID(), NEWID()
   FROM @data
   ;

--------------------------------------------------------------------
-- insert ALL table1 values in one step and catch the new generated guids by OUTPUT clase
INSERT INTO @table1
   SELECT
      T1Id
      ,BinaryValue
   FROM @data d
      JOIN @ids ids ON d.FID = ids.FID
   ;

--------------------------------------------------------------------
-- insert ALL table2 values in one step and catch the new generated guids by OUTPUT clase
INSERT INTO @table2
   SELECT
      T2Id, T1Id, 'don''t know the source for UI-Property'
   FROM @data d
      JOIN @ids ids ON d.FID = ids.FID
   ;

--------------------------------------------------------------------
-- insert ALL table3 values in one step
INSERT INTO @table3
   SELECT
      d.FID, T2Id
   FROM @data d
      JOIN @ids ids ON d.FID = ids.FID
   ;

Grüße
Flo

09.12.2009 - 13:10 Uhr

GUIDs sind kein Problem. 🙂

Da du deine Struktur jetzt "maskiert" hast habe ich leider noch ein paar fragen:

1.)
Dein Cursor wird über den von dir geposteten Snippet gefüllt, oder? An der Stelle steht dann etwas aller:


DECLARE OldHistoryCursor CURSOR FOR
   SELECT f.FID, f.ChangeDate, f.ChangeName, b.BinaryValue, b.ControlName
   FROM foo f
      JOIN bar b ON f.Id = b.FooId

Korrekt?

2.)
Errechnet sich @UIProperty aus irgendwelchen Informationen welche du bei dem vorangehenden INSERT erzeugst?

3.)
Gibt es wirklich keinen Zusammenhang zwischen dem ersten INSERT (@HistoryValueInsert) und den anderen beiden? In deinem Beispiel wird @HistoryValueID unten nicht mehr weiterverwendet. Ist das korrekt oder Kollateralschaden deiner Maskierung? 😁

Hier aber schon mal was als Ansatz für deine Tabelle2 und Tabelle3.

Dabei ist folgendes zu beachten:
Wenn sich die Daten während das Statement läuft parallel ändern können musst du die Daten vor der Verarbeitung in eine Temp-Table schreiben um inkonsistente Reads zu vermeiden. Ein anderer Weg wäre noch Isolation-Level Serializable. Bei der Temp-Table reicht es normalerweise die IDs und nicht alle Werte zwischenzuspeichern.

---=================================================================
-- setup
DECLARE @data TABLE (
   FID UNIQUEIDENTIFIER
   ,ChangeDate DATETIME
   ,BinaryValue IMAGE
   ,ControlName VARCHAR(100)
);
DECLARE @table2 TABLE (
   ID UNIQUEIDENTIFIER
   ,UIProperty VARCHAR(100)
);
DECLARE @table3 TABLE (
   ParentObject UNIQUEIDENTIFIER
   ,Property UNIQUEIDENTIFIER
)

INSERT INTO @data
             SELECT NEWID(), GETDATE(), 0x1, 'ctrl1'
   UNION ALL SELECT NEWID(), GETDATE(), 0x2, 'ctrl2'
   ;

---=================================================================
-- sample

-- table to catch the guids for new rows in table2
DECLARE @t2_ids TABLE (
   Sequence INT NOT NULL IDENTITY(1,1)
      PRIMARY KEY CLUSTERED
   ,ID UNIQUEIDENTIFIER
);

--------------------------------------------------------------------
-- insert ALL table2 values in one step and catch the new generated
-- guids by OUTPUT clase
INSERT INTO @table2
   OUTPUT inserted.ID
   INTO @t2_ids
   SELECT
      NEWID()
      ,'don''t know the source for UI-Property'
   FROM @data
   ORDER BY FID
   ;

--------------------------------------------------------------------
-- insert ALL table3 values in one step
WITH
data_sequenced (FID, RowNum) AS (
   SELECT
      FID
      ,ROW_NUMBER() OVER (ORDER BY FID)
   FROM @data
)
INSERT INTO @table3
   SELECT
      d.FID
      ,t2.ID
   FROM data_sequenced d
      JOIN @t2_ids t2 ON d.RowNum = t2.Sequence

Grüße
Flo

PS: Ist übrigens deutlich weniger Source-Code als dein Cursor. Ist nur anders 😉

09.12.2009 - 12:24 Uhr

Hallo CrudleMilk

Wenn du die Information so in der Datenbank haben möchtest musst du sie "vorberechnen" -- du musst also eine Extra Spalte oder Tabelle machen wo automatisch die gewünschte kombination aus den anderen spalten oder Tabellen eingefügt wird. Das geht dann aber schon mehr Richtung DWH...

Das (= Facts-Tabellen) macht man aber nur in Datawarehouse-Datenbanken. In einer OLTP Datenbank ist dass ein Killer für Übersichtlichkeit, Konsistenz und Performance.

Grüße
Flo

09.12.2009 - 12:22 Uhr

Hi

Frage: Arbeitest du mit IDENTITY Spalten oder mit selbst generierten IDs?
IDENTITY wäre einfacher, eigene IDs gehen aber auch. Wenn eigene IDs, sind die über eine ID-Tabelle verwaltet oder berechnest du die irgendwie?

Ansonsten poste doch mal einen Ausschnitt von deinem Cursor, dann kann ich dir zeigen wie man's umbaut.

Grüße
Flo

09.12.2009 - 11:29 Uhr

Hi

Das parametrisierte INSERT Statement ist soweit korrekt. Ich dachte dass du vielleicht die Werte in den String zusammenbaust.

Zum Cursor:
Bitte dreimal laut sagen: "Cursor sind nie der eleganteste Weg"
Unabhängig vom DBMS sind Cursor immer nur eine Krücke für Frontend-Entwickler wenn man nicht weiß wie man's richtig macht. Wenn du Daten aus drei Tabellen brauchst dann joine diese und mache ein INSERT für alle Daten. Bei extrem großen Datenmengen bietet es sich manchmal an in Blöcken (nicht in Zeilen) zu arbeiten, aber 100.000 Datensätze sind für ein einzelnes INSERT Statement kein Problem.

Grüße
Flo

09.12.2009 - 10:04 Uhr

Hallo Flo

Unterscheide zwischen "anzeigen" und "speichern". Wie JAck30lena schon angesprochen hat solltest du deine Datenbank (wenn's kein DW-System ist) immer normalisiert sein und bleiben. Du kannst so eine Anzeige aber durchaus (über eine View/ein Statement/eine Prozedur/eine Tabellen-Funktion) erzeugen.

Als Ansatz hierfür dürfte dir dieser Thread helfen:
PIVOT über Key Values

Grüße
Flo

09.12.2009 - 09:59 Uhr

Guten morgen DevHB

Gibt es daran etwas auszusetzen, zumindest aus der Ferne betrachtet?

Ja:

Daten mit Select in Cursor

  • ++Cursor++durchlaufen

Zum Cursor ein generelles pfui. Cursor waren, sind und bleiben für immer und ewig ein Anti-Pattern für 99,99% aller Datenbankaufgaben. Wie du schon richtig angesprochen hast solltest du Bulk-Operationen verwenden.

  • innerhalb jedes Durchlaufes ++4 Inserts generieren++und mit ++sp_executesql++und Bindparams ausführen

Wenn du 100.000 Durchläufe hast darin je 4 neue INSERT Statements verwendest heißt dass für SQL Server dass er 400.000 Execution Pläne zu erzeugen hat. Das wir grotten lahm. Zum einen solltest man keine SQL Statements "generieren" sondern parametrisieren. Also ein INSERT mit @parametern wiederverwenden.

Wichtiger bleibt aber der erste Punkt. Der Cursor sollte raus.

Grüße
Flo

08.12.2009 - 20:38 Uhr

Hallo Jörg

Nicht verwechseln. Das DataSet hat grundsätzlich keine Ahnung von irgendwelchen Datenbanken, kann folge dessen auch unmöglich irgendwelche SQL Statements loggen, die bekommt es nie mit. Was du (wahrscheinlich) meinst sind die graphischen Designer um ein typisiertes DataSet zu erzeugen. Das sind Werkzeuge die das DataSet mit ADO.NET DataAdapter-Klassen erweitern. Das DataSet selbst bekommt davon nichts mit. Wenn du die SQL Statements loggen willst musst du auf ADO.NET runter und da bist du halt im Low-Level Bereich.

Grüße
Flo

08.12.2009 - 15:11 Uhr

Hallo Frank

Haben deinen Code jetzt nur überflogen, aber du allokierst Speicher auf dem Heap (AllocHGlobal), gibst diesen aber nicht mehr frei.

Grüße
Flo

08.12.2009 - 13:47 Uhr

Hallo DevHB

Über SQL Server kannst du das fast vergessen. SQLCLR geht nicht weil du die System.Windows.Forms.dll benötigen würdest und die ist nicht Safe und kann somit nicht am SQL Server registriert werden (... ohne Unsafe-Modus und den würde ich unbedingt weglassen!). Ansonsten gäbe es noch den Weg über sp_OA... und Active-X, ist aber auch schrottig.

Nimm ein externes Programm und werte damit dein RTF aus. Das kannst du dann ja über SSIS laufen lassen.

Grüße
Flo

08.12.2009 - 13:06 Uhr

Die Erzeugung der Objekte per factory wäre sicher keine schlechte Lösung aber ich finde es für meinen Fall etwas too much.

Ich finde Factories oft sogar deutlich einfacher. Wenn sich mal irgendwas ändert kann man das zentral an einer Stelle ändern und sofort profitieren alle von der Änderung. Wenn's um ein generelles Basis-Interface geht kannst du auch eine Abstract-Factory verwenden, die kannst du dann gleich für alle Objekte verwenden.

Grüße
Flo

08.12.2009 - 11:15 Uhr

Hallo Jörg

Eigentlich solltest du dein DB-Logging selbst in deine Anwendung einbauen...
Nichtsdestotrotz könnte dir dieser Link weiterhelfen:
http://www.developer.com/net/csharp/article.php/3723011/ADONET-Trace-Logging.htm

Grüße
Flo

07.12.2009 - 17:41 Uhr

Hallo Tom

Die Sortierung ist so schon korrekt. "9" ist größer als "1" und da du Strings sortierst wird von links nach rechts sortiert. Du müsstest beispielsweise eine Regex nehmen, die Zahl extrahieren und über int.Parse konvertieren um nach der zu sortieren.

Grüße
Flo

07.12.2009 - 12:26 Uhr

Hallo Thomas

Erstmal ein paar Fragen:
1.)
Welche Menge an Daten verarbeitet ihr sodass die Umwandlung in Objekte deutlich merklich langsam ist? (Vor allem in einer Web-Applikation?!)
2.)
Habt ihr getestet ob der Datentransfer vom DB-Server auf den Web-Server evtl. das Problem ist? Also mal versucht die SQL Statements mit einem ADO.NET DataReader abgerufen und - zum Test - selbst mal in Objekte gemappt?
3.)
Ein weit verbreiteter Fehler ist immer alle Daten (Spalten) einer Tabelle zu laden. Wenn z.B. eine "Customers"-Tabelle existiert welche alle Adressinformationen des Kunden enthält sind diese oft nur an ein/zwei Stellen des Systems wirklich nötig, an den meisten Stellen benötigt man den Customer nur um ihn mit anderen Daten (z.B. Bestellungen) zu verlinken. Hier immer die kompletten (nicht benötigten) Spalten zu laden ist sehr viel Overhead. Dass kann man massiv optimieren indem man Spalten die nur für spezielle Informationen benötigt werden in untergeordnete Objekte (CustomerAddress?) umzumappen.

Zur Geschwindigkeit unterschiedlicher OR-Mapper:
LINQ2SQL soll um einiges schneller sein als EF, ist aber auf Active-Record und bietet damit deutlich weniger Flexibiltät.
EF v2 (.NET 4.0) soll ein gutes Stück schneller sein, kommt aber erst 03.2010.
NHibernate soll deutlich langsamer sein als EF.
Diese Infos habe ich jetzt nicht selbst evaluiert, sondern basieren auf ein paar Messungen die z.T. hier oder auf Heise und anderen Web-Seiten präsentiert wurden.

Letzten Endes gibt's für die meisten verfügbaren O/R-Mapper aber Grenzen in Sachen Flexibilität, Geschwindigkeit und Optimierungsmöglichkeiten. Irgendwann führt nichts mehr am nativen Ansatz (also ADO.NET) vorbei. Ich will hierzu demnächst einen anderen Ansatz in Form eines Artikels präsentieren, wird aber noch etwas dauern.

Grüße
Flo

07.12.2009 - 09:27 Uhr

Servus

@preli
Sorry, unabhängig vom eventuell nicht 100% richtigen Ergebnis hatte ich dein Statement falsch gelesen. Der von mir angesprochene Inline-Cursor entsteht sobald du werte von außen in die Sub-Query hinein gibst:

SELECT * 
FROM Foo f1
WHERE f1.Id IN (
   SELECT f2.Id
   FROM Foo f2
   -- !!! HIER !!!
   WHERE f2.Id = f1.Id + 1)

An der Stelle entscheidet sich SQL Server normalerweise die Sache als Cursor zu verarbeiten.

Deswegen mein Hinweis auf NOT EXISTS:

SELECT * 
FROM Foo f1
WHERE NOT EXISTS (
   SELECT *
   FROM Foo f2
   -- !!! HIER !!!
   WHERE f2.Id = f1.Id + 1)

Sieht gleich aus, verhält sich aber komplett anders. Hier werde die Daten eben gejoint, wobei ein Semi-Join verwendet wird da man ja nicht wirklich Daten aus f2 zurückliefert sondern nur ihre Existenz prüft. Das ist übrigens auch bei deinem IN-Statement ohne Parameterübergabe an die Sub-Query passiert. ((Witzig, hatte ich noch nicht gewusst das SQL Server das jetzt so auflöst. Bilde mir ein dass das bei SS2k noch anders war.)

Zu dem Execution-Plan mit Id+1 und einer zusätzlichen Computed Spalte muss ich sagen dass ich dein Ergebnis nicht ganz verstehe. Ich habe zwar (warum auch immer, habe ich jetzt nicht weiter erforscht) auch bei beiden Wegen einen Index-Scan, nichtsdestotrotz ist der Weg über die Computed Column bei mir ca. 3-4 mal schneller.

@Humsch
Cursor oder mengenbasiert - wurscht? Versuch's doch mal 😉. Selbst wenn die hier gezeigten mengenbasierten Lösungen einen Index-Scan verursachen (und unterwegs noch Weihnachtsgeschenke kaufen) bleiben sie um ein vielfaches schneller alle jeder Cursor (auch wenn du einen Firehouse Cursor verwendest).

@Xynratron:
LOL! Wusste gar nicht dass du auch eine Tally-Table verwendest (heißt bei mir mittlerweile Numbers wegen häufigen Verwirrungen). Auch dass du auf Jeff's Artikel verweist finde ich witzig. Ist menschlich, fachlich ein super Typ! Ich kann die anderen Artikel von ihm übrigens auch wärmstens empfehlen. 😃

Grüße
Flo

Edit: Typo

04.12.2009 - 17:35 Uhr

Hi

Funktionen (worunter auch "Id + 1" zählt) sollten grundsätzlich in der WHERE Klausel vermieden werden. Da SQL Server keinen Index auf "Id + 1" hat muss er einen Full-Index-Scan durchführen. Daher auch die (sehr schlechte) Performance von 0,5 Sekunden bei 1.000.000 Datensätzen.

@Humsch:
Wenn du's jetzt einmalig benötigst ist das so okay. Wenn du das jedoch regelmäßig brauchst solltest du eine COMPUTED Column auf deiner Tabelle definieren welche "Id + 1" darstellt und diese indizieren.

@preli
Ich bevorzuge entweder einen LEFT JOIN welches fast sicher einen Inline-Cursor vermeidet oder NOT EXISTS da es einen Left Semi Join ausführt, also keine Daten von rechts anfasst.

Grüße
Flo

04.12.2009 - 17:17 Uhr

...Mal gucken wie lange das dauert, bis es verwendet wird^^...

😁
Bin auch mal gespannt was das nächste Thema wird!

04.12.2009 - 15:51 Uhr

Hi Philipp

Nur zur Klarstellung, wollte dich wirklich nicht beirren, es sind aber ein paar Kommentare aus Erfahrungswerten dabei und sowas finde ich immer sehr hilfreich.

Grüße
Flo

04.12.2009 - 12:46 Uhr

Hallo CrudleMilk

zu dem zweiten ... ich dachte es sei besser wenn man den typ explizit angeben würde ... sonst macht er ja wieder das was er will an der stelle und ich kann nicht überprüfen obs evt. doch nicht das is was ich will.

100% korrekt! Die Angabe der Länge von Parametern ist (speziell in deinem Fall) sehr wichtig. Bei Input-Parametern führt eine fehlende Längenangabe "nur" dazu dass SQL Server jedesmal wieder einen neuen Execution-Plan erzeugen muss. Bei Output-Parametern bekommst du schlicht eine Exception wenn du keine Längeninformationen angegeben hast.

Grüße
Flo

04.12.2009 - 10:42 Uhr

Hi Preli

Wenn's dich interessiert, hier gibt's einen (nicht 100% objektiven) Artikel zu dem Thema:
Why Object Databases will always be Tomorrow's Technology

In der Diskussion zu dem Artikel gibt es aber ein paar Erfahrungswerte:
Comments posted to this topic are about the item Why Object Databases will always be Tomorrow's Technology

Grüße
Flo

03.12.2009 - 13:30 Uhr

Hallo Orothred

Das DataGridView bringt da von Haus aus nix mit, kannst du aber selber hinschummeln.

Grundlegender Ansatz:
Zwei Grids untereinander. Das obere mit den Spaltenüberschriften und der Suchzeile. Direkt darunter das Grid mit den Daten.

Beispiel-Code:

   public partial class Form1 : Form {
      bool _suspendColumnSizing;

      public Form1() {
         InitializeComponent();

         dataGridView1.Columns.Add("Col1", "Header1");
         dataGridView1.Columns.Add("Col2", "Header2");
         dataGridView1.Columns.Add("Col3", "Header3");
         dataGridView1.Rows.Clear();
         dataGridView1.ColumnWidthChanged += new DataGridViewColumnEventHandler(dataGridView1_ColumnWidthChanged);

         DataTable table = new DataTable();
         table.Columns.Add("Col1", typeof(int));
         table.Columns.Add("Col2", typeof(string));
         table.Columns.Add("Col3", typeof(DateTime));
         table.Rows.Add(1, "bla", DateTime.Now.AddDays(1));
         table.Rows.Add(2, "bluff", DateTime.Now.AddDays(2));
         table.Rows.Add(3, "blubb", DateTime.Now.AddDays(3));
         dataGridView2.ColumnHeadersVisible = false;
         dataGridView2.DataSource = table;
         dataGridView2.ColumnWidthChanged += new DataGridViewColumnEventHandler(dataGridView2_ColumnWidthChanged);
      }

      void dataGridView2_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e) {
         if (_suspendColumnSizing)
            return;
         _suspendColumnSizing = true;
         dataGridView1.Columns[e.Column.Index].Width = e.Column.Width;
         _suspendColumnSizing = false;
      }

      void dataGridView1_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e) {
         if (_suspendColumnSizing)
            return;
         _suspendColumnSizing = true;
         dataGridView2.Columns[e.Column.Index].Width = e.Column.Width;
         _suspendColumnSizing = false;
      }
   }

Grüße
Flo

02.12.2009 - 20:59 Uhr

Hallo EvilMM

Ich muss gestehen ich habe mir dein Programm jetzt nicht angeschaut, weil derzeit kein Bedarf, aber...

... und "Anforderungs E-Mails" mit O-Ton: "Ich hätte da noch eine Aufgabe für dich" überhaupt nicht - ich denke das können einige hier verstehen. Immerhin bin ich nicht der Auftragsprogrammierer der für kein Geld alles macht...

Wenn jemand eine Anforderung hat kannst du doch den Aufwand schätzen und ein auf Stundensatz basierendes Angebot machen. 😉

Grüße
Flo

02.12.2009 - 20:53 Uhr

Hallo Mr Evil

warum nicht GenericValue<object> - casten kannste dann immernoch #ggg

Wenn du meinst GenericValue<Anything> in GenericValue<object> casten, das geht erst in .NET 4.0

Grüße
Flo

02.12.2009 - 20:51 Uhr

Hallo telnet

Wenn's mal generisch und mal nicht generisch sein soll verwende ein interface:

      interface IValueObject {
         object Value { get; set; }
      }

      class ValueObject<T> : IValueObject {

         public T Value { get; set; }

         object IValueObject.Value {
            get { return this.Value; }
            set { this.Value = (T)value; }
         }
      }

Grüße
Flo

02.12.2009 - 20:05 Uhr

Hallo Olli

Ich schließe mich Mr Evil an. Das ist schon ein sehr triviales Thema und außerdem hört sich deine Frage nach einer fertigen Lösung an. Bitte beachte [Hinweis] Wie poste ich richtig? 1.1.1 und 4.

Wenn's dir um ein Problem bei deiner Lösung geht oder eine bessere Lösung solltest du erstmal deinen Ansatz zeigen.

Grüße
Flo

02.12.2009 - 16:49 Uhr

Hallo SBX

Das nennt sich EAV/CR Datenmodell und wird (meines Wissens nach) von keinem O/R-Mapper richtig unterstütz da das allgemein als altmodisch und ungut betrachtet wird.
(Heißt jetzt aber nicht dass ich das Modell als grundsätzlich schlecht betrachte. Mann sollte - wenn möglich - die Granularität etwas verringern und einen kleinen Framework draufstecken. Problematisch wird EAV/CR beim Thema Auswertungen, da sollte man unbedingt eine flache Datawarehouse DB bereitstellen.)

Hier ein Link zum Thema:
http://ycmi.med.yale.edu/nadkarni/eav_CR_contents.htm

Grüße
Flo

02.12.2009 - 11:38 Uhr

Hallo

Wenn du rausfinden willst welche/wie viele Zeilen in einer Adapter.Update Aktion über INSERT/DELETE/UPDATE betroffen waren kannst du den Adapter.RowUpdated Event verwenden:

      [TestMethod]
      public void TestMethod1()
      {
         
         using (SqlConnection cn = new SqlConnection(Properties.Settings.Default.SandboxLocal)) {
            cn.Open();
            using (SqlDataAdapter adap = new SqlDataAdapter("SELECT TOP(10) * FROM TestData10K", cn))
            using (SqlCommandBuilder builder = new SqlCommandBuilder(adap)) {
               DataTable table = new DataTable();
               adap.Fill(table);
               table.Rows[0].Delete();
               table.Rows[1]["SomeInt"] = 123;
               table.Rows[2]["SomeDateTime"] = DateTime.Now;
               DataRow row = table.NewRow();
               row["SomeVarChar"] = "Hello World";
               table.Rows.Add(row);

               adap.RowUpdated += new SqlRowUpdatedEventHandler(adap_RowUpdated);
               adap.Update(table);
            }
         }
      }

      void adap_RowUpdated(object sender, SqlRowUpdatedEventArgs e) {
         Console.WriteLine("Id: {0} | Action: {1}", e.Row["Id"], e.StatementType);
      }

Grüße
Flo

01.12.2009 - 16:14 Uhr

Hallo msycho

(Es gibt übrigens auch ein eigenes Datenbank-Forum 😉 )

Wenn du SQL Server 2005 oder 2008 hast wäre es erstmal wichtig von NTEXT auf XML (gibt's auch) als Datentyp umzustellen (NTEXT ist obsolet und fliegt nach SS2k8 raus). Zum Manipulieren kannst du dann auch XQuery verwenden, damit vermeidest du dass immer alles geschrieben wird.

Zu deiner Frage bzgl. der "Größe":
Für den XmlSerializer ist's sicher nicht zu groß, der ist beschränkt auf das was deine Hardware aushält. Wenn du auf den DB-Spaltentypen XML umstellst kannst du auch einen XmlReader als Wert übergeben, dann kannst du die Daten wenn nötig auch streamen.

Grüße
Flo

01.12.2009 - 12:36 Uhr

Hi

Ich würde immer mit der Klasse anfangen da das die Schicht ist mit der du primär arbeiten wirst. Die Serialisierung ist danach Thema und wenn's nicht ordentlich 1:1 serialisierbar ist dann pack einfach einen Mapper dazwischen.

Grüße
Flo

01.12.2009 - 12:29 Uhr

Hallo Peter

Geschwindigkeit ist aber nocht nicht so der Brüller, wobei ich hier den LogParser als Bremser im Verdacht habe. Das Einspeisen von ca. 15.000 Datensätzen aus einer GPS-Logdatei in meine DB dauert ca. 4 sec.

Wenn dir die Performance nicht ausreicht kannst du mit SQL Server 2008 (auch Express) Table-Valued Parameters verwenden. Habe dazu letztens einen Artikel geschrieben:
Table-Valued Parameters - A Performance Comparison
Kam bei 400.000 Datenbankaktionen auf 7 Sekunden 😉

Grüße
Flo

30.11.2009 - 21:52 Uhr

8o
Ich sage jetzt besser gar nichts mehr...

30.11.2009 - 21:12 Uhr

Hallo daichan

Was bekommst du für eine Fehlermeldung wenn du versucht die Datenbank am SQL Server anzubinden?

BTW: BCP ist ein sehr schnelles Kommandozeilen-Tool mit dem man Daten aus und in einem SQL Server im-/exportieren kann. (Kommandozeile aufmachen "bcp" eingeben Enter drücken, da siehst du die Hilfe). Bringt aber nix wenn die DB nicht am SQL angebunden ist.

Grüße
Flo

30.11.2009 - 21:09 Uhr

@Florian Reisch**(e)**l
Dann muss die 2er deinstalliert worden sein. Die gehört nämlich zur Standardinstallation. Siehe z.B.
>
.

🤔
Danke für die Info, versteh' aber nicht wo die hingekommen ist ?(

30.11.2009 - 21:06 Uhr

Hi Khalid

Früher hat man zum Thema Datenbank noch bestimmte Leute gefragt, heute meint hier irgendwie jeder, dass er/sie eine Datenbank designen können.
Danke. Den Satz müsste ich auf DIN A1 ausdrucken und an die Hauswand hängen 🙂

😁 😁 😁

Ich selber setze auch O/R Mapper ein. Allerdings nur bei kleineren Projekten (z.B. kleine (Konfigurations-)Tools). Bei Enterprise Lösungen (n-Tier) bin ich momentan weg von O/R Mappern.

Ich muss gestehen ich bin auch immer noch hin und her gerissen. Einerseits finde ich die Tools gut und empfehle sie auch - gerne - weiter, anderseits habe ich noch keinen gefunden der wirklich alle Wünsche erfüllt. Damit meine ich jetzt nicht alle Abfragen handeln können sondern die nötige Transparenz und Flexibilität bietet.

Wir arbeiten im Büro zwar exklusiv mit einem O/R-Monster (weil vorgeschrieben) das bringt aber oft viele Probleme die man ohne das Ding eigentlich wesentlich besser lösen könnte. (Ladezeit des Controllers > 2 Sekunden, deutlich mehr als 30MB Speicherverbrauch OHNE das irgendwelche Daten geladen sind, keine PI, ...)

Ich habe mir die letzten Tage mal die DSL Tools für VS2008 genauer angeschaut und muss gestehen dass mir ein ordentliches Modellierungstool und ein paar Transformationen mit ein Hilfsklassen mindestens(!) genauso hilfreich erscheinen. (Überlege zur Zeit ob ich vielleicht einen Artikel dazu schreiben soll.) In VS2010 werden die Features zur Modellierung ja noch ungleich mächtiger.

Wobei ich mir demnächst ganz intensiv das EF 2.0 anschauen will, da es für n-Tier wohl einige gute Möglichkeiten bietet.

Ist ein Thema das auch noch auf meiner Todo-Liste steht. Was ich bislang mitbekomme bietet's viele schicke (und hilfreiche) neue Möglichkeiten, muss man aber mal genauer anschauen.

... Ich kann gezielt Stellen optimieren. Was ich bei einem O/R Mapper gar nicht, oder nur bedingt machen kann.

Eben das fehlt mir irgendwie bei allen ORMs aktuell auch.

In meiner alten Firma haben wir teils über Wochen nur an DB-Designs und SQL Statements gearbeitet und mehr nicht. Sowas vermisse ich irgendwie.

*seufz*

Grüße
Flo

30.11.2009 - 20:50 Uhr

Hi Xynratron

Ich glaube wirklich, dass allein aufgrund dass es solche Tools gibt, die auf einem bestimmten Gebiet weniger bewanderten Programmierer sich da trozdem rantrauen. ("Kann ja nicht so schwer sein" - genau wie so mancher Heimwerker denkt er wäre ein Zimmermann)

Zum rantasten ist das auch total okay. Speziell im Privatgebrauch oder wirklich keinen Projekten sind ORMs eine tolle Hilfe eine Lösung zu bekommen ohne einen Dunst von der Materie zu haben. Wenn man in größeren Projekten jedoch eine komplexe Technologie wie Datenbanken einsetzen will finde ich sollte man wissen (oder wissen wen man Fragen kann) wie so ein Monster tickt. Ich arbeite jetzt seit ca. 15 Jahren mit SQL Server und lerne bis heute immer wieder neue Eigenheiten kennen.

(BTW: Natürlich kann ich heimwerken wie ein gelernter Zimmermann! 😄 )

Aber ich denke, diese Entwicklung wird sich wieder umkehren. Spätestens dann, wenn man mal mit einem Projekt so richtig auf die Nase gefallen ist und sich entschliesst einen Spezialisten zu fragen. (Obwohl, wann lernt der Mensch eigentlich wirklich aus seinen Fehlern?)

Ich weiß nicht auf wie viele Menschen das zutrifft. Wir haben zwar alle durch hinfallen das Laufen gelernt. Sich nach Fehlern selbst reflektieren und zu überlegen was man selber falsch gemacht hat ist eine Eigenschaft die ich leider nicht sehr vielen Leuten zuschreiben würde.

...Z.B. bemerke ich, dass es nicht mehr wirklich viele junge Leute gibt, die mit richtig Herzblut bei der IT sind - für die ist das ein Beruf wie jeder andere geworden.

Da kann ich jetzt glücklicherweise das Gegenteil bei mir im Büro berichten. Mein Ex-Azubi war/ist wirklich an der ganzen Sache interessiert und beschäftigt sich wesentlich mehr mit der Materie (auch mit Themen die er erstmal vielleicht nicht braucht) als er müsste.

Viele Grüße
Flo

30.11.2009 - 20:30 Uhr

Servus Siassei

Der Grundgedanke hinter eines O/R-Mapper ist doch, eine Objekt-Orientierte-Anbindung zu Datenbank zu schaffen. Die Anbindung soll unabhängig von der DB sein.

Sehr richtig. In kleinen Projekten kann es ja durchaus sinnvoll sein (weil weniger Aufwand) wirklich ein 1:1 Mapping zu haben. Darum existieren Mapper wie LINQ2SQL oder Castle-ActiveRecord. In einem komplexen Domain-Model kann das aber zum echten Problem werden.

...Es wird einfach zu wenig Zeit in die Entwicklung von O/R-Mapper investiert! Weshalb das Problem bestehen bleibt. Mir kommt es so vor, als wäre ein O/R-Mapper ein billiges und einfaches Tool, dass man besitzen muss. Aber dem ist nicht so 😛

Ich glaube an der Stelle bin ich jetzt (aufgrund persönlicher Erfahrung) anderer Meinung 😁 . Bei uns wird viel zu viel Zeit in den ORM investiert um ihn immer mehr zur eierlegenden Wolmilchsau zu machen. Weil viel zu viele Sonderfälle direkt in den OR-Controller gepackt wurden statt sie einfach in einem einzelnen Projekt spezifisch zu handeln strotzt das Ding derzeit von mehr als 200(!!) Methoden die kein Mensch mehr versteht.

Ich finde ein ORM sollte solide 90-95% abfackeln können. Alles darüberhinaus ist so speziell dass es - meiner Meinung nach - mehr Sinn macht eine Speziallösung darüberzustecken. Ein guter ORM zeichnet sich für mich dadurch aus die Möglichkeit zu bieten ihn (z.B. durch spezielle SQL Lösungen) zu erweitern. Das sind (und sollen bleiben) natürlich Sonderfälle aber die sind manchmal wichtig. Wenn das dann bedeutet, dass man diese Speziallösung für ein anderes DBMS portieren muss kann's das trotzdem wert sein.

Hibernate (kenne ich nur aus Java) bietet zum Beispiel die Möglichkeit, die Abbildung auf der Datenbank zu steuern und anzupassen. Sprich, es muss das Modell nicht 1:1 auf die Datenbank übertragen werden. Für jede DB eine Config-Datei (*.xml) manuell schreiben und gut ist es.

Falls du's mal brauchst, können NHibernate und EF (besser in V2) auch.

Danke dir für dein Feedback
Flo

30.11.2009 - 20:06 Uhr

Hallo DotNet

Also ich habe auf W7 nur PowerShell 1.0

Grüße
Flo

30.11.2009 - 17:53 Uhr

Hi Xynratron!

Heute kann man vom SQL-Server erfahren wie oft er sich wünscht, dass da ein Index auf einer Tabelle liegen würde. Könnte es nicht passieren, dass die Evolution weitergeht, und die O/R-Mapper in Zukunft sowas sogar selbst auswerten und entsprechende Optimierungen anbieten/vornehmen?

Wenn das mal passiert dürfte der Weg dahin aber noch lang sein. Die Missing-Index-Vorschläge von SQL Server sind noch nicht ganz optimal, hier übertreibt er an manchen Stellen. Außerdem beeinträchtigt die Generierung eines Index ja massiv die Performance des laufenden Systems. Nur in der Enterprise Edition ist es möglich einen Index wirklich ONLINE zu erzeugen und die hat ja ihren Preis.

Ein zusätzlicher Faktor, den es auch immer mal wieder in der Menschheitsgeschichte gab: Spezialisierung auf Aufgaben.

Wenn das bei euch so ist (auch wenn's nicht 100% funktioniert) beglückwünsche ich dich. Hier kommt es mir eher umgekehrt vor. Früher hat man zum Thema Datenbank noch bestimmte Leute gefragt, heute meint hier irgendwie jeder, dass er/sie eine Datenbank designen können. Geht halt schick über die Oberfläche vom ORM (der dann natürlich 1:1 die Objekt-Struktur auf die Datenbank steckt) und Leute die fast nur Web-Seiten entwickeln erstellen Datenbanken.

Wenn jetzt eben ein schlechtes Datenbank-Design herauskommt, weil ein Programmierer nur noch mit einem O/R-Mapper rumhantiert, dann ist das doch im Gegenzug eigentlich das Problem, dass zu geeigneter Zeit der DB-Spezialist nicht die Finger drauf hatte.

Wie schon gesagt. "Der DB-Spezialist" ist hier durch "den O/R-Mapper" und "den Designer vom ORM" ersetzt worden. Ich weiß nicht ob wir damit alleine stehen oder ob das weitläufig so ist.

Grüße
Flo

30.11.2009 - 13:42 Uhr

Hi Chris

Ich habe das selber noch nicht gebraucht da ich Setups verwende und alle Assemblies lokal kopiere, kann dir leider nicht bis in's letzte Detail helfen. Aber die Richtung kann ich dir geben:
Start->Programme->Systemsteuerung->Verwaltung->Microsoft .NET Framework 2.0 Konfiguration
(hoffe das war jetzt richtig übersetzt, weil's bei mir alles englisch ist)

Grüße
Flo

30.11.2009 - 13:08 Uhr

Hi sth_Weird

Danke dir für deine Antwort.

Ich fange nicht zwingend mit der Datenbank an wenn ich ein neues Projekt (oder ein neue Feature) aufbaue. Ich fange oft (meistens) mit dem Domain-Model an. Es muss nur klar sein, dass das Domain-Model nicht (zwingend) was mit dem Datenbank-Model zutun haben muss.

Der Designer der Datenbank sollte halt auch wissen was eine Datenbank ist und wie sie tickt (das kann sich sogar von DBMS zu DBMS sehr unterschiedlich verhalten). Datenbankdesign ist einfach ein Thema das auf Erfahrung basiert. Viele Verwender von O/R-Mappern vergessen das leider oft und das halt zu massiven Problemen führen.

Schön zu sehen, dass ich mit meiner Meinung nicht alleine stehe. 🙂

Grüße
Flo

30.11.2009 - 11:49 Uhr

Hallo Juy Juka

Danke dir für deine Antwort!

Zu deiner Aussage "langer Post... nichts gesagt":
Kann man nicht sagen. Ich wollte ja explizit ein paar unterschiedliche Meinungen hören 😃

Grüße
Flo

30.11.2009 - 10:10 Uhr

Hallo Fabian

Include liefert immer das Parent (also RosterAlgorithm in deinem Fall) zurück. Fogedessen wird dein "Pharmacy" auf dieser Ebene gesucht. Du musst den Pfad auf einmal angeben:

.Include("PharmacyToRosterAlgorithm.Pharmacy")

Grüße
Flo

PS: Das ist übrigens genau kein Lazy Loading (und das ist wahrscheinlich auch gut so) 😉

29.11.2009 - 18:39 Uhr

Hallo herbivore

Ich spreche ja gar nicht davon, dass "die Schüler schlechter werden" und will auch keinen Generations-Krieg beginnen. Ich sehe es bei mir in der Arbeit, dass selbst Entwickler der älteren Generation (>40) Fehler machen die sie früher nie gemacht hätten. Ich weiß, damit schließe ich meinen Wunsch nach dem "Prüfung" als Lösung aus, weil sie's theoretisch ja wissen.

Nichtsdestotrotz werden die Tools manchmal überschätzt/überfordert. Das wäre wie wenn jeder Dateien nur noch mit File.ReadAllText() verarbeiten würde oder man sagen würde "GUI Controls nur das könnten was der Designer" hergibt. (Die beiden Beispiele treffen es ganz gut.) Ich habe schon mehrfach (hier oder beim mir im Büro) Aussagen gehört wie "Das kann der Designer/Mapper (ist jetzt Platzhalter) nicht also geht's nicht"

Außerdem ist Datenbankdesign ja nicht das Ziel eines O/R-Mappers. Maximal die Erzeugung eines Basis-Schemas.

Die Tools sind gut und sollen verwendet werden, ich sehe nur oft dass sie schlicht durch eine schlechte Verwendung unter Beschuss geraten. Ich hatte letztens erst eine längere Diskussion in einem anderen Forum bei der NHibernat und ORMs im ganzen beschimpft wurden, am Ende hat sich rausgestellt dass die ganzen Probleme auf falscher Verwendung basierten.

ORMs nehmen viel Arbeit ab aber man sollte (wie beim Autofahren) den Kopf nicht ausschalten.

Grüße
Flo

29.11.2009 - 18:04 Uhr

Hi Gü

hab ich schon mal gelesen und wenn ich mich recht erinnere ist nichts neues drin. Desweitern wird in den einführenden Kapiteln eigentlich alles erwähnt und im Rest des Buches dies mehr oder weniger nur wiederholt.

Ist ja in den meisten dieser Bücher so. Werde es wohl trotzdem lesen. Oft wird man einfach wieder an etwas vergessen das man einfach ein bisschen verdrängt hat. Außerdem, lieber bekomme ich 5mal die gleiche Info als einmal eine nicht 😃.

Grüße
Flo

29.11.2009 - 18:01 Uhr

Hi herbivore

Ich will darauf hinaus, dass die Probleme von denen ich spreche auch schon vor dem Aufstieg der O/R-Mapper theoretisch existierten da die entsprechenden Gefahren (wie Lazy Loading) nicht erst mit O/R-Mappern aufkamen. Früher ist - meiner Meinung nach - jedoch bewusster mit dem Thema umgegangen worden.

Ich habe ja schon geschrieben, ich bin selbst ein Freund dieser Technologie, aber man sollte die Thematik unter dem Tool nicht vergessen. Wäre ungefähr so als würden wir alle verlernen zu laufen seit es Autos gibt (was oft durchaus zutrifft)

...gleich ein Untergangszenario ("schleichender Tod") zu konstruieren.

Die Formulierung habe ich bewusst gewählt 😉
Ich glaube manchmal muss man erstmal mit einer Aussage polarisieren/aufrütteln um ein gutes Ergebnis zu erzielen (was in diesem Fall diese Diskussion ist)

Grüße
Flo

29.11.2009 - 17:14 Uhr

Hi

Gerade drüber gestolpert:
Microsoft Application Architecture Guide, 2nd Edition

Da ich's selber gerade erst gefunden habe kann ich noch nicht sagen ob es gut ist oder nicht, aber ich kann mir nicht vorstellen dass MS ein Schrott-Buch zu so einem Thema rausbringt. (In der zweiten Auflage.)

Dachte mir könnte den einen oder anderen interessieren.

Grüße
Flo

29.11.2009 - 16:48 Uhr

Hi Gü

A fool with a tool is still a tool.

War ist und bleibt für immer wahr. 😉

... würden sicherlich andere Alternativen gefunden werden - mit ähnlichen Problemen

Der Witz ist (für mich), dass die früher verbreiteten Probleme wie nicht parametrisierte Queries, SQL Injection, sonstige grundlegende SQL Fehler meiner Meinung nach durch O/R-Mapper recht gut abgefangen werden. Dafür werden heute Fehler gemacht die es früher nicht gab. Hat vielleicht damit zutun dass das Arbeiten mit O/R-Mappern einfach zu einfach erscheint. Früher ist mal jemand von seinem Platz aufgestanden und hat wen anders gefragt wenn es um ein Datenbankdesign, eine knifflige Abfrage oder was ähnliches ging.

Vielleicht gab es die Probleme früher auch nicht weil eine der einbezogenen Personen auf diese Probleme automatisch hingewiesen hat... Ist jetzt 'ne Theorie die mir gerade so in den Sinn kommt.

Grüße
Flo

29.11.2009 - 16:15 Uhr

Hi %

Mich würde mal interessieren ob ich der einzige bin der sich manchmal wünschen würde, dass man O/R-Mapper in der IDE erst über eine Prüfung freispielen muss?

Versteht mich nicht falsch. Ich finde O/R-Mapper gut um einiges an Leichtsinnsfehlern zu vermeiden. Nichtsdestotrotz sind und bleiben es Tools wie andere. Vor dem verbreiteten Erscheinen von O/R-Mappern haben sich viele Entwickler, meiner Meinung nach, deutlich mehr Gedanken um das R in O/R gemacht. Das geht mit dem Datenbankdesign los - welches einfach nicht immer 1:1 auf eine Relationale Datenbank übertragen werden sollte - und endet mit falsch angewendetem Lazy-Loading. ... um jetzt nur zwei Punkte zu nennen.

Würde mich über eure Meinung freuen!

Grüße
Flo

29.11.2009 - 12:11 Uhr

Hi

Wie schon gesagt, bei PDA als echten Service-Host habe ich so meine Zweifel. Was ich mir eher vorstellen könnte wäre dass permanente (durable) Host-/Client-Verbindungen funktionieren wobei der Client ebenfalls zum Host wird, aber nur für einen Client (den eigentlichen Service-Host). Letzte Alternative die man mit WCF versuchen könnte wären noch Callback-Operationen - auch hier wird der Client zum Host aber in einer nochmals abgespeckten Variante. Habe ich aber beides noch nicht selbst eingesetzt - müsstest du also mal versuchen.

Wenn das alles nix hilft gäbe es noch andere Kommunikationsmöglichkeiten wie SMS/Mail wobei man hier vorsichtig sein muss, da eine zeitnahe Zustellung zwar normal aber nicht vertraglich zugesichert ist.

Dann gäbe es noch die Einbindung von irgendwelchen Chat-Clients wie ICQ oder so. Hierbei natürlich keine sensiblen Daten übertragen, sondern einfach irgendeine GUID oder so. Danach kann der PDA immer noch eine normale Service-Verbindung öffnen.

Zu guter letzt bleibt nur noch das gute alte Polling. Unter dem Aspekt, dass das im Web durchaus ein de facto Standard ist wird's zwar nicht schöner, ist es aber valide.

Ich sehe da einiges an Evaluierung auf dich zukommen... (Falls hier nicht jemand anders bereits Erfahrung damit hat.)

Hoffe ich konnte wenigstens ein paar Anregungen geben!

Grüße
Flo

PS: Hatte vorhin vergessen: Willkommen überhaupt bei myCSharp.de 😃