Laden...

TwoWay Binding Datagrid To WCF Service?

Erstellt von s0h0 vor 13 Jahren Letzter Beitrag vor 13 Jahren 5.086 Views
S
s0h0 Themenstarter:in
683 Beiträge seit 2006
vor 13 Jahren
TwoWay Binding Datagrid To WCF Service?

Hallo,

ich hab gerade erst angefangen mich mit WCF zu beschäftigen also bitte seit net zu mir 😃

Was fehlt mir damit die am Client geänderten Daten zum Service und dann in die DB übernommen werden?

Danke schonmal.

Ich habe einen WCF Service der genau eine Funktion bereitstellt welche mir eine Klasse zurueck giebt. Die Klasse erbt von INotifyPropertyChanged und hat als einzigsten DataMember eine ObservableCollection mit den Entitys aus der DB.


//Service.svc.cs
 static testdatenEntities _context = null;
        private static testdatenEntities Get_context()
        {
            if (_context == null)
            {
                _context = new testdatenEntities();
            }

            return _context;
        }

        public customData GetDataFromADO()
        {
            var _context = Get_context();
            var result = (from lief in _context.LIEF_INFO.AsQueryable()
                          orderby lief.LNR
                          select lief).Skip(50).Take(50);

            ObservableCollection<LIEF_INFO> _ocol = new ObservableCollection<LIEF_INFO>(result);
        
            customData customdata = new customData() { List = _ocol };

            return customdata;
        }

//IService.cs
    [DataContract]
    public class customData : INotifyPropertyChanged
    {
        ObservableCollection<LIEF_INFO> _ocol = new ObservableCollection<LIEF_INFO>();
        [DataMember]
        public ObservableCollection<LIEF_INFO> List
        {
            get { return _ocol; }
            set { _ocol = value; }
        }


        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyChange(params object[] properties)
        {
            if (PropertyChanged != null)
            {
                foreach (string prop in properties)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(prop));
                }
            }
        }
    }

Am Client binde ich das Ergebnis der Asynchronen Service Abfrage an ein DataGrid.


         service.GetDataFromADOAsync();
            service.GetDataFromADOCompleted += new EventHandler<GetDataFromADOCompletedEventArgs>(service_GetDataFromADOCompleted);
       

          void service_GetDataFromADOCompleted(object sender, GetDataFromADOCompletedEventArgs e)
        {
            var binding = new Binding();
            binding.Source = e.Result;
            binding.Path = new PropertyPath("List");
            binding.Mode = BindingMode.TwoWay;
            dataGrid1.SetBinding(DataContextProperty, binding); 
        }

Eine Null kann ein bestehendes Problem verzehnfachen

3.728 Beiträge seit 2005
vor 13 Jahren
WCF-Dienst

Hallo s0h0,

die Datenkontrakte bei WCF werden serialisiert und übers Netzwerk übertragen. Das heißt, dass der Client eine Kopie der Collection bekommt. Diese Kopie liegt nun im Arbeitsspeicher des Client-Computers und hat mit dem ursprünglichen Objekt auf dem Server nichts mehr zu tun!

Du musst eine Save-Methode in Deinen WCF-Dienst einbauen, welche die geänderten Daten entgegen nimmt. Diese musst Du am Client explizit dann aufrufen, wenn Du Änderungen übers Netzwerk zurück zum Server übertragen willst.

Das ADO.NET Entity Framework eigent sich nicht gut für verteilte Anwendungen. Deshalb möchte ich Dir empfehlen, das lieber mit typisierten DataSets zu machen. Hintergünde dazu findest Du hier: Eine kleine Geschichte zum Thema RowState und OR-Mapper

S
s0h0 Themenstarter:in
683 Beiträge seit 2006
vor 13 Jahren

Danke fuer deine Antwort Rainbird.

Also du meinst ich sollte auf der Service Seite die Daten nicht ueber das Entity Framework holen sondern ganz normal über SQLConnection? Und dem Client ein typisiertes DataSet uebertragen?

Hast du noch andere Belege fuer deinen Artikel? Denn wir stehen hier vor einer Design Entscheidung... Wir lesen zu 85% die Daten nur aus der Rest ist Daten Manipulation. Es geht aber um sehr große Datenmengen, überwiegt dann vielleicht doch wieder der Vorteil der schlankeren Objekte?

Grüsse

Eine Null kann ein bestehendes Problem verzehnfachen

3.728 Beiträge seit 2005
vor 13 Jahren

Also du meinst ich sollte auf der Service Seite die Daten nicht ueber das Entity Framework holen sondern ganz normal über SQLConnection? Und dem Client ein typisiertes DataSet uebertragen?

Ja, das meine ich.

Wenn Du nur im LAN kommunizierst und nicht interoperabel sein musst, dann würde ich Dir auch .NET Remoting statt WCF als Kommunikationsframework empfehlen. Grund für diese Empfehlung ist, dass WCF im Vergleich zu Remoting ein komplizierter Kram ist (dafür beitet WCF allerdings mehr Möglichkeiten; Aber brauchst Du die alle?). Hier ein einfaches Remoting-Beispiel für den Einstieg (ist nicht für große Enterprise-Applikation gedacht, zeigt aber wie einfach und effektiv Remoting ist): Remoting-Helfer

Aber WCF ist auch okay. Ist am Ende Geschmacksache.

Hast du noch andere Belege fuer deinen Artikel?

Schau Dir mal die Beispiele im Web zu Entity Framework mit WCF an. Du findest kein Beispiel, welches mehrere Änderungen für eine Geschäftstransaktion an einem Stück zurück zum Server überträgt (und wenn, dann ist es ein umständliches Gewurstel; Siehe weiter unten: Self Tracking Entities). Alle haben sie in ihren WCF-Services zum Speichern von Änderungen Methoden wie


public void CreateCustomer(Customer customer)
{ ... }

public void UpdateCustomer(Customer customer)
{ ... }

public void DeleteCustomer(Customercustomer)
{ ... }

Das ist in meinen Augen "Hello World". Schon wenn ich drei Angebotspositionen in einem DataGrid speichern will, bin ich mit dem Ansatz am Ende. Daran, für jede Position UpdateSalesOfferItem aufzurufen und jedes Mal Netzwerkoverhead, Aufwand für Authentifizierung und Erstellen der Dienstinstanz in Kauf zu nehmen, will ich erst gar nicht denken. Vom Transaktionshandling will ich gar nicht anfangen.

Beispiel: Entity Framework in geschichteten Architekturen

Microsoft liefert mit .NET 4.0 eine Erweiterung Namens Self Tracking Entities aus. Damit wird bei den EF-Entities der RowState nachgerüstet. Wenn man aber sieht, wie viel Code der Code-Generator dafür erzeugt, dann kann von "schlanken" Objekten nicht mehr die Rede sein.
Das "fette" von den DataSets (was aber kein überschüssiges Fett ist) wird nun durchs Hintertürchen ins EF hineingetragen. Die selben Leute, die DataSets verdammt haben, weil sie ihnen zu "fett" waren, kommen plötzlich ins Schwärmen und lobpreisen die "neuen" Möglichkeiten des EF.

Hier findest Du Infos zu Self Tracking Entities: Working with Self-Tracking Entities

Ganz so toll ist das EF aber scheinbar doch nicht. Würde sich sonst Jörg Neumann von thinktecture die Mühe machen, ein ganz eigenes Persistenzframework zu schaffen, welches für verteilte Szenarien ausgelegt ist (Siehe CodePlex: Thinktecture.DataObjectModel)? Wohl kaum.
Allerdings frage ich mich, warum es denn unbedingt auf biegen und brechen was mit herkömmlichen Objekten sein muss, wenn es seit .NET 1.0 bereits eine exzellente Persistenzlösung gibt? Ich denke inzwischen, dass das ein "Modetrend" ist. In seinem Artikel in der DOTNETPRO (Ausgabe 04/2010) zur obigen Persistenzlösung schreibt er

In der Vergangenheit war hier in der Regel das DataSet der kleinste gemeinsame Nenner. Doch in Zeiten von WCF und O/R-Mapping ist dessen Verwendung nicht mehr sinnvoll; in Silverlight ist diese Klasse nicht einmal mehr enthalten. Warum der Einsatz nicht mehr sinnvoll sein soll, schreibt er allerdings nicht. Dass die Klasse nicht in Silverlight enthalten ist, hat nichts zu sagen. Im Compact Framework sind auch nicht alle Klassen vorhanden. Silverlight enthält auch viele andere Klassen nicht, die er vermutlich auch verwendet, wenn er gerade nicht Silverlight programmiert.

Ähnliche Aussagen über DataSets findet man leider überall im Web. Es hält sich auch immernoch bei einigen das Gerücht, dass DataSets intern mit XML arbeiten würden und deshalb langsam sein. Phantasie mit Schneegestöber. DataSets haben eine gute XML-Unterstützung, was aber nicht automatisch heißt, dass intern mit XML-DOM o.ä. gearbeitet wird.

Denn wir stehen hier vor einer Design Entscheidung...

Dann solltest Du Dir die Mühe machen und mit beiden Technologieen eine kleine Beispielanwendung schreiben.

Es gibt noch ein paar generell Punkte, die eine Entscheidung für oder gegen DataSets beeinflussen:

Wenn verschiedene RDBMS (z.B. SQL Server, Oracle, MySQL) unterstützt werden müssen, hat EF mit seiner Abstraktionsschicht einen großen Vorteil. Man programmiert nur noch gegen das Modell und der jeweilige EF-Provider erzeugt automatisch zur Laufzeit SQL. Je nach Qualität des EF-Providers und nach Komplexität der Aufgabenstellung, kommt man damit auch schon mal an die Grenzen. Auch mit dem EF bleibt ein Wechsel des RDBMS ganz ohne Änderungen am Code eine Utopie. Trotzdem vereinfacht die Abstraktion das Ganze erheblich. Aber sie kostet dafür auch Ressourcen.

Als Argument gegen DataSets wird noch oft angeführt, dass sie wegen der Affinität zum .NET Typsystem in heterogenen Umgebungen (z.B. in Verbindung mit Java) problematisch sind. Das wird in real-word Projekten aber vermutlich selten zum tragen kommen, da man eh nicht alle Aspekte der heterogenen Umgebung in seine Anwendung (oder sein Modul) hineintragen will. Integrationsschichten, Adapter (oder wie man es sonst noch nennt) sorgen da für die Umsetzung in neutrale Datenformate für die Kommunikation.

Ich habe auch schon gehört, DataSets seien obsolet, deprecated oder sonst irgendwie "veraltet". Das ist Quatsch mit Soße. DataSets sind fester Bestandteil von ADO.NET. DataSets kommen auch in Verbindung mit ganz aktuellen Technologieen wie z.B. Visual Studio Tools for Office zum Einsatz. Wer gerne LINQ einsetzt, kann dies dank Linq2DataSet auch mit DataSets tun: MSDN: Linq2DataSet Samples
Hier die Microsoft Roadmap für Datenzugriffstechnologieen (Stand Dez. 2009): MSDN: Data Access Technology Roadmap
DataSets und ADO.NET stehen nicht auf der deprecated/obsolete Liste. 😁

DataSets sind sehr flexibel. Auch an typisierte DataSets kann man zur Laufzeit ganz leich neue Spalten zufügen. Bei EF ist alles hartcodiert und in Stein gegossen.

Typisierte DataSets fühlen sich nicht "klassisch objektorientiert" an. Die erzegten Klassen heißen z.B. CrmDataSet, CrmDataSet.CustomerDataTable und CrmDataSet.CustomerDataRow. Diese Namenskonventionen mögen manche Leute nicht. Das ist aber kein DataSet-Problem, sondern eine Problematik von Code-Generatoren allgemein.

Wir lesen zu 85% die Daten nur aus der Rest ist Daten Manipulation. Es geht aber um sehr große Datenmengen, überwiegt dann vielleicht doch wieder der Vorteil der schlankeren Objekte?

In Sachen Geschwindigkeit schlägt klassisches ADO.NET mit DataAdapter und DataTable/DataSet das EF um ein Vielfaches. Schau Dir mal die folgende Grafik an: Entity Framework and LINQ to SQL Performance
Die Abstraktionsschicht des EF wird zu einem hohen Preis erkauft.
Für große Datenmengen ist das fast schon ein KO Kriterium.
Was die Serialisierung und Übertragung übers Netzwerk angeht, sind DataSets nicht größer, als andere Objekte auch (DataSets sind auch nur Objekte). Es werden ja nur die Daten serialisiert. DataSets speichern noch den OriginalValue, aber das müssen Self Tracking Entities auch tun.

Ich hoffe meine Informationen helfen Dir weiter.

S
s0h0 Themenstarter:in
683 Beiträge seit 2006
vor 13 Jahren

Hallo,

ersteinmal vielen Dank für deine hilfreiche Antwort. Ich habe sie noch am selben Tag gelesen, wollte mir das allerdings erst ein wenig anschaun bevor ich antworte. Die Links waren auch sehr interessant.

Ziel ist es eine bestehende WindowsAnwendung in eine "moderne" 😃 Silverlight Anwendung zu megrieren. Remoting kommt nicht in Frage da es eine Business Internet Anwendung wird. WCF ist als SOA Element gesetzt. Die Frage ist vorallem wie übertragen wir die Daten und wie fragt der Service die Daten ab. Hauptaufegenmerk liegt dabei auf Performance und Netzwerk Traffic.

Inzwischen hatte sich auch die Problem/Fragestellung etwas geaendert. Jedoch nicht die möglichen Lösungswege die du angesprochen hast.
Performante Dynamische Abfrage auf die Datenbank, welche eine unterschiedliche Anzahl an Spalten zurueckgeben kann.
Diese drei Ansätze habe ich implementiert und gemessen.

  • Bei Linq2Sql Abfrage wird die gesamte Klasse mit 120 Spalten generiert und übertragen auch wenn nur 10 Spalten Werte gesetzt/selektiert wurden.

  • Abfrage mit SqlDataAdapter und den result Table in eine CustomClass schreiben und zum Client übertragen; Am Client ist die Ling2Entities Klasse aus dem WCF Service in den Metadaten verfügbar. Die custom class Liste mittels reflection zur Linq Entity Liste Mappen.

  • DataSet übertragung macht Probleme da es in SL4 nur als ArrayOfXElements ankommt. (Ein typisiertes DataSet zu uebertragen waere nach der Lektüre deines Artikels und der Links mein Favorit gewesen).

  1. Antwortzeit vom WCF Service bei beiden Methoden nahezu identisch (Trotz des mappings vom table zur customclass). Bei mehreren Testläufen schwankende Ergebnisse je nach Server auslastung...

  2. Das Mapping am Client, in der 2. Methode, von CustomClass zu LinqEntity dauert zwischen 9-13ms.

  3. In Fiddler sehe ich das die uebertragene LinqEntity Liste fast doppelt soviele Daten hat wie die customclass Liste. Da in der customClass nur die Werte und Spaltennamen uebertragen werden die selektiert wurden...

Nach den momentanen Ergebnissen tendiere ich eher zu der Abfrage mit dem DataAdapter und dem Mapping.

Wenn du dazu eine Meinung hast höre ich sie gerne 😃

Noch der Vollstaendigkeit wegen:
//ServiceContract Abfrage und Mapping im Service...


  public ReturnTable GetDataAdapter_CustomClass(int rowCount, int columnCount, string groupby, List<KeyValuePair<string, string>> where)
        {
            var con = new SqlConnection(ConnectionString);
            var query = CreateQueryString(rowCount, columnCount, groupby, where); 
            var adapter = new SqlDataAdapter(query, con);
            var pubs = new DataSet();

            con.Open();
            adapter.Fill(pubs);

            return ConvertTableToCustomClass(pubs.Tables[0]);
        }
        

        public ReturnTable ConvertTableToCustomClass(DataTable dt)
        {
            int cellcount = 0;
            var returnTable = new ReturnTable();
            foreach (DataRow dr in dt.Rows)
            {
                var rows = new Dictionary<string, string>();
                foreach (object dc in dr.ItemArray)
                {
                    rows.Add(dt.Columns[cellcount].ColumnName, dc.ToString());
                    cellcount++;
                }

                returnTable.List.Add(rows);
                cellcount = 0;
            }
            return returnTable;
        }

//Mapping im Client


        void _service_GetDataDataAdapterCompleted(object sender, GetDataAdapter_CustomClassCompletedEventArgs e)
        {
            stopwatch.Stop();

            stpWatchConvert.Start();
            List<v_LIEF> liste = ConvertToClassList(e.Result);
            stpWatchConvert.Stop();

            EndServiceRequest();

            gv_data.ItemsSource = null;
            gv_data.ItemsSource = liste;
        }

        public List<v_LIEF> ConvertToClassList(ReturnTable returnTable)
        {
            var list = new List<v_LIEF>();

            foreach (Dictionary<string, string> row in returnTable.List)
            {
                var ent = new v_LIEF();
                foreach (KeyValuePair<string, string> kvp in row)
                {
                    try
                    {
                        if (!string.IsNullOrEmpty(kvp.Value))
                        {
                            Type typ = ent.GetType();
                            PropertyInfo pi = typ.GetProperty(kvp.Key);
                            if (pi != null)
                            {
                                if (pi.PropertyType.GetGenericArguments().Length > 0 && pi.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
                                {
                                    Type genericType = pi.PropertyType.GetGenericArguments()[0];
                                    pi.SetValue(ent, Convert.ChangeType(kvp.Value, genericType, null), null);
                                }
                                else
                                {
                                    pi.SetValue(ent, Convert.ChangeType(kvp.Value, pi.PropertyType, null), null);
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show("Cant map variable " + kvp.Key + "       " + ex.ToString());
                    }
                }

                list.Add(ent);
            }
            return list;
        }

Eine Null kann ein bestehendes Problem verzehnfachen

3.728 Beiträge seit 2005
vor 13 Jahren
DataReader

Hallo s0h0,

Du verschenkst mit dem DataAdapter+Mapping Ansatz einen Haufen Performance! Statt eines DataAdapters solltest Du DataReader zum lesen und direkte Commands zum speichern verwenden.

Du lässt Dir vom Adapter eine DataTable erzeugen, nur um daraus eine Liste mit eigenen Objekten zu erzeugen. Es ist wesentlich schneller Deine Objekte direkt aus den Daten eines DataReaders zu erzeugen. Hier ein Beispiel, wie der DataReader eingesetzt wird (allerdings ohne WCF): How to Fill a DataGrid in Silverlight 4 with SQL DataReader

Es ist Schade, dass SL4 keine DataSets unterstützt, aber daran kann man nichts ändern.

S
s0h0 Themenstarter:in
683 Beiträge seit 2006
vor 13 Jahren

Hallo Rainbird,

du hast recht der DataReader schlägt die andere Variante mit dem DataTable meistens nochmal um einige Millisekunden. (Allerdings nicht immer, ich denke aber das liegt an der Last auf dem SQLServer)

Danke dir für deine Unterstuetzung, hat mir sehr weiter geholfen.

Damit mein momentaner Favorit 😃
//Service


   public CustomTable GetDataReader_CustomTable()
        {
            var customTable = new CustomTable();

            try
            {
                con.Open();
                var query = ServiceHelper.CreateQueryString();
                var sqlComm = new SqlCommand(query, con);
                SqlDataReader dataReader = sqlComm.ExecuteReader();

                if (dataReader.HasRows == true)
                {
                    for (int i = 0; i < dataReader.FieldCount; i++)
                    {
                        customTable.Columns.Add(dataReader.GetName(i));
                    }

                    while (dataReader.Read())
                    {
                        var values = new Object[dataReader.FieldCount];
                        int fieldCount = dataReader.GetValues(values);

                        var list = (from obj in values where !string.IsNullOrEmpty(obj.ToString()) select obj.ToString()).ToList();
                
                        customTable.Rows.Add(list);
                    }
                }
            }
            catch (SqlException e)
            {
                var error = e.Message;
            }
            finally
            {
                con.Close();
            }

            return customTable;
        }


 [DataContract]
    public class CustomTable
    {
        private List<string> columns = new List<string>();
        [DataMember]
        public List<string > Columns
        {
            get { return columns; }
            set { columns = value; }
        }

        private List<List<string>> rows = new List<List<string>>();
        [DataMember]
        public List<List<string>> Rows
        {
            get { return rows; }
            set { rows = value; }
        }
    }

//Client Mapping


        public List<v_LIEF> ConvertToClassList(CustomTable customTable)
        {
            var list = new List<v_LIEF>();

            foreach (var row in customTable.Rows)
            {
                var ent = new v_LIEF();
                Type typ = ent.GetType();
                int columnIndex = 0;
                foreach (string cell in row)
                {
                    if (!string.IsNullOrEmpty(cell))
                    {
                        PropertyInfo pi = typ.GetProperty(customTable.Columns[columnIndex]);
                        if (pi != null)
                        {
                            if (pi.PropertyType.GetGenericArguments().Length > 0 &&
                                pi.PropertyType.GetGenericTypeDefinition().Equals(typeof (Nullable<>)))
                            {
                                Type genericType = pi.PropertyType.GetGenericArguments()[0];

                                pi.SetValue(ent, Convert.ChangeType(cell, genericType, null), null);
                            }
                            else
                            {
                                pi.SetValue(ent, Convert.ChangeType(cell, pi.PropertyType, null), null);
                            }
                        }
                    }
                    columnIndex++;
                }
                list.Add(ent);
            }

            return list;
        }

Eine Null kann ein bestehendes Problem verzehnfachen