Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Portal
  • |
  • Mitglieder
Beiträge von bb1898
Thema: [erledigt] Entity Framework 6: Ergebnis von ExecuteSqlCommand wird nicht angezeigt
Am im Forum: Datentechnologien

Nochmalige Suche in der EF-Dokumentation hat wohl die entscheidende Stelle zutage gefördert:

Zitat
Note that DbSet and IDbSet always create queries against the database and will always involve a round trip to the database even if the entities returned already exist in the context. A query is executed against the database when:

It is enumerated by a foreach (C#) or For Each (Visual Basic) statement.
It is enumerated by a collection operation such as ToArray, ToDictionary, or ToListenter link description here.
LINQ operators such as First or Any are specified in the outermost part of the query.
The following methods are called: the Load extension method on a DbSet, DbEntityEntry.Reload, and Database.ExecuteSqlCommand.

When results are returned from the database, objects that do not exist in the context are attached to the context. If an object is already in the context, the existing object is returned (the current and original values of the object's properties in the entry are not overwritten with database values).
Finding entities using a query

Das dürfte der Knackpunkt sein. Es widerspricht im Übrigen auch meinem alten Lehrbuch nicht, wird dort halt nur nicht erwähnt (an der Stelle liegt der Fokus auf dem Vermeiden unnötiger Datenbankabfragen).

So dass ich also eine der von @Marco_GR aufgezählten Varianten brauche (Danke schön!). Damit scheint mir das Problem gelöst, ich habe den Thread als erledigt markiert.

Thema: [erledigt] Entity Framework 6: Ergebnis von ExecuteSqlCommand wird nicht angezeigt
Am im Forum: Datentechnologien

Zitat von Abt
Zitat von https://msdn.microsoft.com/en-us/library/jj592907(v=vs.113).aspx
Sending raw commands to the database

Note that any changes made to data in the database using ExecuteSqlCommand are opaque to the context until entities are loaded or reloaded from the database.
Ein Kontext kann einfach nicht das tracken, was Du an ihm vorbei schleust.

Das ist mir nicht neu, aber:
Zitat
So far you’ve used LINQ to query a DbSet directly, which always results in a SQL query being sent to the database to load the data.
Quelle: Lerman, Julia; Miller, Rowan: Programming Entity Framework : DbContext. O'Reilly Media, 2012. Kap. 2: Querying with DbContext; Querying Local Data. Blöderweise finde ich in meiner EBook-Ausgabe keine Seitenzahl dafür.

Daraus schließe ich doch, dass die Abfrage nach der Ausführung des SQL-Kommandos die Entities eben gerade neu aus der Datenbank lädt.
Zitat
Warum Du einen ORM verwendest, um dann SQL Code Raw zu senden, erschließt sich mir nicht.
Das Verhalten ist - so wie ich das sehen - genau das.

So weit ich sehen kann, ist das die einzige Möglichkeit, aus dem EF heraus die Ausführung einer gespeicherten Prozedur anzustoßen. Und an einer Stelle in meinem Programm soll eben das stattfinden.

Thema: [erledigt] Entity Framework 6: Ergebnis von ExecuteSqlCommand wird nicht angezeigt
Am im Forum: Datentechnologien

verwendetes Datenbanksystem: PostgreSQL 10.2, .NET-Provider Npgsql

Hallo,

ich möchte in einem WPF-Programm folgendes machen:

- In einem DataGrid werden Datensätze angezeigt.
- Auf Knopfdruck wird in der zugrundeliegenden Datenbank eine gespeicherte Prozedur ausgeführt, die Felder in eben diesen Datensätzen ändert.
- Die Datensätze werden mit einer neuen Abfrage wieder aus der Datenbank geholt und im gleichen DataGrid angezeigt.

Für den ganzen Vorgang wird eine Instanz der passenden von DbContext abgeleiteten Klasse benutzt, die beim Öffnen des Fensters erzeugt und beim Schließen zerstört wird.

Das funktioniert so nicht: nach der zweiten Abfrage werden im DataGrid die unveränderten Daten angezeigt. Ich kann mich aber davon überzeugen, dass die gespeicherte Prozedur richtig ausgeführt wurde, z.B. indem ich das Fenster schließe und neu öffne.

Also habe ich ein kleines Konsolenprogramm gebastelt, das etwas Ähnliches tut:

- Abfrage an die Datenbank, Ergebnisse anzeigen
- Direkter UPDATE-Befehl an die Datenbank (context.Database.ExecuteSqlCommand(...))
- Wiederholung von Abfrage und Ergebnisanzeige

Tut richtig, wenn die drei Aktionen in getrennten DbContext-Instanzen ablaufen; ich nehme sehr stark an, dass ich die ersten beiden auch zusammenfassen könnte, habe das aber noch nicht probiert. Läuft alles im gleichen Kontext ab, dann hat die zweite Abfrage das gleiche Ergebnis wie die erste.

Fragen, die ich mit Hilfe der Dokumentation nicht lösen konnte:

- Was genau passiert da eigentlich? Ich lese immer und überall, dass Abfragen an den DbContext an die Datenbank geschickt werden - wenn man es anders haben will, muss man das extra einrichten. Auch wenn der SQL-Befehl in seiner eigenen Transaktion ausgeführt wird, müsste eine Abfrage, die danach abgesetzt wird, doch das Ergebnis schon kennen? Oder wird vielleicht der SQL-Befehl ohne mein ausdrückliches Zutun in einem eigenen Thread ausgeführt und das "danach" stimmt so eben nicht?

- Ist eine neue DbContext-Instanz das Mittel der Wahl oder geht es auch anders?

Ich hänge das Konsolenprogramm mal an, obwohl der Beitrag sowieso schon lang ist. Es benutzt eine PostgreSQL-Version der Northwind-Datenbank, daraus nur drei Felder der Tabelle "Customers". Die zwei getrennten Namensräume sind hier natürlich ein schlechter Witz.

Danke für Hinweise aller Art!

#######################################################################

//Customer.cs
using System;

namespace Nordwind_Daten
{
    public class Customer
    {
        public string CustomerId { get; set; }
        public string CompanyName { get; set; }
        public string Region { get; set; }

        public override string ToString() => String.Format("Customer Id {0}, Company {1}, Region {2}",
                CustomerId, CompanyName, Region);
    }
}

//CustomerMap.cs
using System.Data.Entity.ModelConfiguration;

namespace Nordwind_Daten
{
    internal class CustomerMap : EntityTypeConfiguration<Customer>
    {
        public CustomerMap()
        {
            ToTable("customers");
            Property(c => c.CustomerId).HasColumnName("customerid")
                .IsFixedLength().HasMaxLength(5).IsRequired();
            Property(c => c.CompanyName).HasColumnName("companyname")
                .HasMaxLength(40).IsRequired();
            Property(c => c.Region).HasColumnName("region").HasMaxLength(15);
        }
    }
}

//NordwindDB.cs
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace Nordwind_Daten
{
    public class NordwindDB : DbContext
    {
        static NordwindDB()
        {
            Database.SetInitializer<NordwindDB>(null);
        }

        public NordwindDB() : base("name=NWConn")
        { }

        public DbSet<Customer> CustomerSet { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
            modelBuilder.HasDefaultSchema("public");
            modelBuilder.Configurations.Add(new CustomerMap());
        }

    }
}

//Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Npgsql;
using NpgsqlTypes;
using Nordwind_Daten;

namespace ExecSql_Nordwind
{
    class Program
    {
        static string GetCustomersByIdStart(string idstart, NordwindDB CDB)
        {
            var sb = new StringBuilder();
            //using (var CDB = new NordwindDB())
            //{
                var query = from cust in CDB.CustomerSet
                            where cust.CustomerId.StartsWith(idstart)
                            orderby cust.CustomerId
                            select cust;
                foreach (var cust in query)
                {
                    sb.AppendLine(cust.ToString());
                }
            //}
            return sb.ToString();
        }

        static int ChangeRegion(string idstart, string newchar, NordwindDB CDB)
        {
            string sql = "UPDATE customers SET region = CONCAT(region, :newchar) " +
                "WHERE customerid LIKE :idstart";
            //using (var CDB = new NordwindDB())
            //{
                var par_nc = new NpgsqlParameter(":newchar", NpgsqlDbType.Char, 1);
                var par_ids = new NpgsqlParameter(":idstart", NpgsqlDbType.Char, 2);
                par_nc.Value = newchar;
                par_ids.Value = idstart + "%";
                int cnt = CDB.Database.ExecuteSqlCommand(sql, par_nc, par_ids);
                return cnt;
            //}
        }

        static void Main(string[] args)
        {
            string newchar = "*";
            string appchar = "C";
            using (var CDB = new NordwindDB())
            {
                Console.WriteLine(GetCustomersByIdStart(appchar, CDB));
                int z = ChangeRegion(appchar, newchar, CDB);
                Console.WriteLine("Ergebnis von ChangeRegion: {0}", z);
                Console.WriteLine(GetCustomersByIdStart(appchar, CDB));
            }
            Console.Write("Beenden mit beliebiger Taste ...");
            Console.ReadKey();
        }
    }
}

Thema: ObservableCollection: Inhalt austauschen
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Das ging ja fix! Danke an alle Antwortenden.

Zitat von Th69
bzgl. der Exception. Dies liegt daran, daß bei der ObservableCollection<T> die virtuelle Methode InsertItem überschrieben ist

Und daher führt ((List)Items).AddRange(ships) keine PropertyChanged/CollectionChanged-Ereignisse aus, welches wiederum für die Konsistenz nötig sind.

Danke für die Klärung! Dann habe ich also die Wahl zwischen einer Schleife mit Add-Aufrufen und einer neuen Instanz. Na schön.

@T-Virus: Ich habe die eigene Subklasse ShipsCollection nur erstellt, um an die Items-Eigenschaft heranzukommen, die ist ja protected. Aber nachdem das nicht tut, was ich mir davon erhofft habe, ist die Klasse nicht mehr nötig.

Thema: ObservableCollection: Inhalt austauschen
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Aufgabe: Der Inhalt einer ObservableCollection soll auf Kommando komplett ausgetauscht werden (meist als Ergebnis einer Suche in einem größeren Datenbestand).

Das funktioniert, wenn die neuen Objekte der Liste einzeln zugefügt werden:

public void Refill(IEnumerable<Ship> ships)
{
	Clear();
	foreach (var ship in ships)
	{
		Add(ship);
	}
}
Eigentlich läge es doch aber nahe, die neuen Objekte in einem Rutsch zuzufügen, mit AddRange oder so. Die ObservableCollection hat allerdings keine Methode, die das tut, ich habe in der Dokumentation jedenfalls nichts dergleichen gefunden. Versucht habe ich dies hier:

public class ShipsCollection : ObservableCollection<Ship>
{
	public ShipsCollection() : base() { }

	public ShipsCollection(IEnumerable<Ship> ships) => Refill(ships);

	public void Refill(IEnumerable<Ship> ships)
	{
		Clear();
		((List)Items).AddRange(ships);
	}
}
Das wird zwar anstandslos kompiliert, das Programm startet auch und das Ergebnis der ersten Suche wird richtig angezeigt. So bald aber eine neue Suche ein anderes Ergebnis hat, gibt es eine Exception. Weil der interessante Teil des Textes dazu recht lang ist, stelle ich erst mal meine Frage und hänge ihn danach an:

Stimmt meine Vermutung, dass so ein Komplett-Austausch der Elemente einer ObservableCollection nicht möglich ist oder habe ich nur den richtigen Weg nicht gefunden?

Klar ist, dass ich eine neue Instanz der Collection mit neuem Inhalt erzeugen könnte, aber dann müsste ich INotifyPropertyChanged auf die Collection als Ganzes anwenden. Und wäre das überhaupt eine gute Idee?

Und hier der gekürzte Text zur Exception:
Fehler
System.InvalidOperationException ist aufgetreten.
HResult=0x80131509
Nachricht = Ein ItemsControl ist nicht konsistent mit seiner Elementquelle.
Weitere Informationen finden Sie in der inneren Ausnahme.
Quelle = <Die Ausnahmequelle kann nicht ausgewertet werden.>
...
Die Ausnahme wurde ausgelöst, da der Generator für Steuerelement 'System.Windows.Controls.DataGrid Items.Count:5' mit dem Namen '(unbenannt)' eine Reihe von CollectionChanged-Ereignissen empfangen hat, die nicht mit dem aktuellen Status der Elementsammlung übereinstimmen. Die folgenden Unterschiede wurden festgestellt:
Gesammelte Anzahl 1 unterscheidet sich von der tatsächlichen Anzahl 5. [Gesammelte Anzahl ist (Anzahl bei letztem Reset + #Adds - #Removes seit letztem Reset).]

Eine oder mehrere der folgenden Quellen haben möglicherweise falsche Ereignisse ausgelöst:
System.Windows.Controls.ItemContainerGenerator
System.Windows.Controls.ItemCollection
System.Windows.Data.ListCollectionView
* ShipsCrud.ShipsCollection
(Die beteiligten Quellen werden als die wahrscheinlichere Ursache des Problems betrachtet.)

Die häufigsten Ursachen umfassen (a) das Ändern der Sammlung oder deren Anzahl ohne Auslösen eines entsprechenden Ereignisses sowie (b) das Auslösen eines Ereignisses mit falschem Index- oder Elementparameter.

Thema: SQLite: Query für Zeitspanne spart gültigen Eintrag aus
Am im Forum: Datentechnologien

Zitat von GeneVorph
Zitat
Aber dass deine beiden Queries im Startpost syntaktische Fehler haben, hast du gelesen?

Vielleicht bin ich vollkommen blind, aber ich verstehe nicht, wieso es beim ersten query funktioniert, beim zweiten nicht^^

Da würde ich mich jetzt gern anhängen: ich dachte eigentlich, ich wüsste, was an den beiden Abfragen auf genau die gleiche Weise falsch ist, aber warum Du bei der ersten richtige Ergebnisse (oder überhaupt ein Ergebnis) bekommen hast, bleibt mir ein Rätsel.

Der "DB Browser for SQLite" teilt mir bei einer vergleichbaren Fehlkonstruktion sofort mit, dass hier ein Syntaxfehler vorliegt. Ebenso sqlite3.exe. Dasselbe würde ich eigentlich von jedem derartigen Programm erwarten.

Thema: Unterschied: Methodenaufruf im Parameter/ Variable im Parameter
Am im Forum: Grundlagen von C#

Ich weiß, der Thread ist nicht mehr druckfrisch, aber im Hinblick auf spätere Leser:
Du übergibst in keinem Fall eine Methode als Parameter; die hätte einen ganz anderen Datentyp. Du übergibst jedesmal das Ergebnis eines Methodenaufrufs, nur gibst Du dem einmal einen eigenen Namen, einmal nicht. Die Vorteile des ersten Vorgehens wurden schon genannt.

Thema: Entity Framework Code First - Eine von zwei verknüpften Entitäten hat als ID in der DB "0"
Am im Forum: Datentechnologien

Zitat von Abt
Wenn das Feld Id heissen würde, würde das EF automatisch erkennen, dass es sich hier um den Key handelt und ein Auto-Increment erfolgt.
PersonId erkennt das Ef nicht automatisch.

Das würde mich in der Klasse Person doch arg wundern. Die von Dir genannte Quelle sagt jedenfalls bei den Konventionen (Hervorhebung von mir):
Zitat
The default convention for primary key is that Code-First would create a primary key for a property if the property name is Id or <class name>Id (NOT case sensitive).
(Entity Framework Code First Conventions

Thema: Nachschlagetabelle mit String-Schlüssel + Wechsel des Arbeitsbereichs = Oberflächlicher Datenverlust
Am im Forum: GUI: WPF und XAML

Und noch eine Variante mit etwas Code-behind: Der Detail-Grid bekommt wieder CurrentMainItem als DataContext; die beiden Comboboxen werden benannt und bekommen ihre ItemsSource im Code-behind zugewiesen. Da ich nicht sicher bin, ob die Zuweisung im Konstruktor evtl. zu früh kommen könnte (bevor der DataContext für das ganze UserControl gesetzt ist), habe ich eine Methode UserControl_Loaded dafür eingeführt.

Da bleiben die Werte in den Comboboxen bzw. die Lookup-Objekte im Datensatz beim Hin- und Zurückschalten erhalten.

Thema: Wert von Combobox2 via SelectedIndex aus Combobox1 auslesen
Am im Forum: GUI: WPF und XAML

Wieso eine zweite Combobox, wenn es dort für jeden Gegenstand der ersten Combobox nur einen Eintrag gibt, also nichts zu wählen? Warum nicht einfach so:


<Combobox ItemsSource={Binding Gegenstandsliste}
          SelectedItem={Binding AktuellerGegenstand} />
<TextBox Text={Binding AktuellerGegenstand.Gewicht} />

Wenn das Gewicht nicht verändert werden soll, tut's auch ein TextBlock statt der TextBox.

Oder entspricht der "Gegenstand" in der ersten Combobox genau genommen einer Gruppe von Gegenständen mit verschiedenem Gewicht? Das könnte dann zum Beispiel so gehen:

<ComboBox ItemsSource={Binding Gegenstandsgruppenliste}
          SelectedItem={Binding AktuelleGegenstandsgruppe} />
<ComboBox ItemsSource={Binding AktuelleGegenstandsgruppe}
          DisplayMemberPath="Gewicht"
          SelectedItem={Binding AktuellerGegenstand} />

AktuellerGegenstand enthält in diesem zweiten Fall den Gegenstand mit dem gewählten Gewicht.

Thema: Nachschlagetabelle mit String-Schlüssel + Wechsel des Arbeitsbereichs = Oberflächlicher Datenverlust
Am im Forum: GUI: WPF und XAML

Die Variante mit den Objekten als Member funktioniert dann und nur dann, wenn ich im XAML den Extra-Kontext für den Detail-Grid weglasse; das heißt, sie funktioniert unter der gleichen Bedingung wie die Variante mit den IDs.

Mit dem separaten DataContext wird es aber interessant: dann sind nämlich nach dem Hin- und Herschalten beide Comboboxen leer, nicht nur eine. Beide Lookup-Member im CurrentMainItem sind dann null. Dazu vermute ich, dass in meinem ursprünglichen Beispiel der Integer-Schlüssel einfach deshalb nicht zu null wird, weil er das ja nicht kann - er ist ja kein nullable int.

In einem meiner WPF-Bücher habe ich den folgenden Hinweis gefunden:
"Greifen Sie auf ein ListBoxItem zu, enthält dieses das eigentliche Objekt im DataContext. Wird nun in der ListBox nach unten gescrollt, sodass das ListBoxItem nicht mehr sichtbar ist, wird es aufgrund der UI-Virtualisierung vom Visual Tree entfernt. Der DataContext ist dann ungültig." (Huber, Windows Presentation Foundation 4.5, S. 649).
Ersetze "nach unten gescrollt" durch "UserControl durch ein anderes ersetzt" und wir sind nah dran. Nur sehe ich auch mit dieser Information noch nicht, warum der Rückgriff auf den UserControl-DataContext via RelativeSource den entscheidenden Unterschied macht.

Thema: Nachschlagetabelle mit String-Schlüssel + Wechsel des Arbeitsbereichs = Oberflächlicher Datenverlust
Am im Forum: GUI: WPF und XAML

So ist es. Auch mein ursprüngliches Programm funktioniert mit dieser Änderung. Aber warum? Das verstehe ich genau so wenig wie ich vorher den Fehler verstanden habe.

Dass das XAML so deutlich aufgeräumter aussieht, stimmt; allerdings ist die Entsprechung zum "MainItem" im ursprünglichen Programm ein Objekt mit deutlich mehr Properties, die zu bearbeiten sind - und die brauchen jetzt alle die längere Pfadangabe. Deswegen ja der Extra-Kontext.

Kann jemand in meinem ursprünglichen XAML einen Fehler sehen? Prinzipiell falsch ist ein eigener DataContext für einen Teilbereich eines Fensters meines Wissens nicht, und prinzipiell falsch ist die ItemsSource für die Comboboxen auch nicht gesetzt - oder doch? Die Art, wie sich das unkorrigierte Programm verhält, ist ja von vornherein arg seltsam: nur beim Wegschalten vom Daten-UserControl und wieder Zurückkehren, und dann ist der vorher gewählte Satz intakt bis auf diese eine Combobox mit der String-Property als SelectedValue.

Thema: Nachschlagetabelle mit String-Schlüssel + Wechsel des Arbeitsbereichs = Oberflächlicher Datenverlust
Am im Forum: GUI: WPF und XAML

Da ist es. Falls etwas fehlt, was nötig wäre, bitte sagen! Die .sln-Datei habe ich erst mal weggelassen, weil die gleiche Projektmappe noch ein weniger minimales Beispiel enthält; bei Bedarf nehme ich das Zeugs noch mal auseinander.

Jedenfalls Danke für's Hineinschauen!

Thema: Nachschlagetabelle mit String-Schlüssel + Wechsel des Arbeitsbereichs = Oberflächlicher Datenverlust
Am im Forum: GUI: WPF und XAML

Die Situation:

Das Hauptfenster der Anwendung enthält einen Bereich für wechselnde UserControl-Instanzen, die für verschiedene Tätigkeiten da sind. Außerdem Schalter zum Wechseln der Tätigkeit.

Ein UserControl ist dazu da, Sätze aus einer Liste anzuzeigen, einen davon auszuwählen und zu bearbeiten. Die Tabelle hat drei Fremdschlüssel, die sie mit drei kurzen und stabilen Nachschlagetabellen verknüpfen; zum richtigen Setzen der Fremdschlüssel gibt es jeweils eine Combobox, die die Sätze der Nachschlagetabelle zeigt. Eine der Nachschlagetabellen hat einen kurzen String als Primärschlüssel und das scheint der Knackpunkt zu sein.

Das arbeitet alles so, wie es soll, so lange ich in diesem einen Arbeitsbereich bleibe. Nennen wir das mal das Bearbeitungsfenster. Das Umschalten auf einen anderen Bereich klappt auch, nur:

Wenn ich vom Bearbeitungsfenster weg- und dann wieder zu ihm hinschalte, dann ist in der Combobox, die zu dem String-Schlüssel gehört, nichts mehr ausgewählt. Im Debugger sehe ich, dass der entsprechende Fremdschlüssel in diesem Satz jetzt null ist. Der ViewModel-Typ, in den die EF-Entities eingepackt sind, erlaubt das Wiederherstellen der Originaldaten, und das stellt dann auch den Fremdschlüssel richtig wieder her. Komplett verschwunden ist er also nicht.

Ich würde gern wissen, ob jemand schon mal auf so eine Sorte Problem gestoßen ist und mehr über die möglichen Ursachen weiß als ich. Passende Suchbegriffe wären auch eine große Hilfe; ich habe auf StackOverflow hauptsächlich nach Fragen zur Combobox bzw. zu "SelectedValue" gesucht, aber das hat nichts ergeben.

Abhilfemöglichkeiten, die kein Verständnis des Problems erfordern, sehe ich mehrere, Verständnis wäre mir aber wichtiger.

Ein Minimalbeispiel, das das Problem demonstriert, benutzt Daten aus einer XML-Datei, nicht aus einer Datenbank, und nur zwei, nicht drei Nachschlagetabellen. In seinen Modellklassen gibt es keine Navigationseigenschaften, nur die Properties für die Fremdschlüssel (so weit man ohne Datenbank von solchen überhaupt reden sollte). Es hat nur Modellklassen, die INPC implementieren, keine drum herum gepackten ViewModel-Klassen; und es gibt keine Bearbeitung, die Sätze werden nur angesehen.

Die wichtigsten Teile des Minimalbeispiels:

Haupt-Datenklasse (Basisklasse VMBase implementiert INPC, sonst tut sie nichts)


public class MainItem : VMBase
{
	private int _id;
	private string _name;
	private double _price;
	private int _iLookupId;
	private string _sLookupId;

	public int Id {
		get { return _id; }
		set { SetProperty(ref _id, value); }
	}
	public string Name {
		get { return _name; }
		set { SetProperty(ref _name, value); }
	}
	public double Price
	{
		get { return _price; }
		set { SetProperty(ref _price, value); }
	}
	public int ILookupId
	{
		get { return _iLookupId; }
		set { SetProperty(ref _iLookupId, value); }
	}
	public string SLookupId
	{
		get { return _sLookupId; }
		set { SetProperty(ref _sLookupId, value); }
	}
}
Nachschlagetabelle mit Integer-Schlüssel


public class ILookup
{
	public int Id { get; set; }
	public string Name { get; set; }
}
Nachschlagetabelle mit String-Schlüssel


public class SLookup
{
	public string Id { get; set; }
	public string Name { get; set; }
}
Einlesen und Bereithalten der Daten:


public class DataAccess
{
	const string xmlname = @"D:\home\sibylle\dokumente\dbspiel\sibdata_1.xml";

	public ICollection<MainItem> MainItems { get; private set; }
	public ICollection<ILookup> ILookups { get; private set; }
	public ICollection<SLookup> SLookups { get; private set; }
	public int MaxId { get; private set; }

	public DataAccess()
	{
		ILookups = new List<ILookup>();
		SLookups = new List<SLookup>();
		MainItems = new List<MainItem>();
		LoadData();
	}

	private void LoadData()
	{
		... // Lesen aus einer XML-Datei, verteilen auf die drei Listen
	}
ViewModel für den Bearbeitungsbereich (IPageVM ist eine Schnittstelle, die nur die Eigenschaft PageName bereitstellt; wird für das Umschalten zwischen Arbeitsbereichen im Hauptfenster benutzt)


class DataPageVM : VMBase, IPageVM
{
	private DataAccess _data;
	private MainItem _currentMainItem;

	public string PageName { get { return "Datenseite"; } }
	public ObservableCollection<MainItem> AllMainItems { get; private set; }
	public List<ILookup> AllILookups { get; private set; }
	public List<SLookup> AllSLookups { get; private set; }

	public MainItem CurrentMainItem
	{
		get { return _currentMainItem; }
		set { SetProperty(ref _currentMainItem, value); }
	}

	public DataPageVM()
	{
		_data = new DataAccess();
		AllMainItems = new ObservableCollection<MainItem>(_data.MainItems);
		AllILookups = _data.ILookups as List<ILookup>;
		AllSLookups = _data.SLookups as List<SLookup>;
		CurrentMainItem = AllMainItems.First();
	}
}
Und die XAML-Datei für den Bearbeitungsbereich

<UserControl x:Class="UCWechsel_XML.DataUC"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:UCWechsel_XML"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid Background="LightGoldenrodYellow">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="2*"/>
                <ColumnDefinition Width="3*"/>
            </Grid.ColumnDefinitions>
            <Border BorderBrush="DarkGreen" BorderThickness="1" Margin="5">
                <StackPanel>
                    <Label Content="Alle Sätze" />
                    <ListBox ItemsSource="{Binding AllMainItems}"
                     SelectedItem="{Binding CurrentMainItem}"
                     DisplayMemberPath="Name"
                     IsSynchronizedWithCurrentItem="True" />
                </StackPanel>
            </Border>
            <Border Grid.Column="1" BorderBrush="DarkGreen" BorderThickness="1" Margin="5">
                <StackPanel>
                    <Label Content="Details" />
                    <Grid DataContext="{Binding CurrentMainItem}" >
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Label Content="SLookup" />
                        <ComboBox Grid.Column="1" 
                                  ItemsSource="{Binding DataContext.AllSLookups, 
                                      RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
                                  DisplayMemberPath="Name"
                                  SelectedValuePath="Id"
                                  SelectedValue="{Binding SLookupId}" VerticalContentAlignment="Center" />
                        <Label Grid.Row="1" Content="ILookup" />
                        <ComboBox Grid.Row="1" Grid.Column="1" 
                                  ItemsSource="{Binding DataContext.AllILookups, 
                                      RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
                                  DisplayMemberPath="Name"
                                  SelectedValuePath="Id"
                                  SelectedValue="{Binding ILookupId}" VerticalContentAlignment="Center" />
                        <Label Grid.Row="2" Content="Name" />
                        <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Name}" VerticalContentAlignment="Center" />
                        <Label Grid.Row="3" Content="Price" />
                        <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Price}" VerticalContentAlignment="Center" />
                        <TextBlock Grid.Row="4" Text="ID" Margin="0,3" Padding="5" />
                        <TextBlock Grid.Row="4" Grid.Column="1" Text="{Binding Id}" Margin="0,3" VerticalAlignment="Center"/>
                    </Grid>
                </StackPanel>
            </Border>
        </Grid>
    </Grid>
</UserControl>

Thema: Hat C# unter Windows 10 Probleme mit SQL Server 2016 ?
Am im Forum: Datentechnologien

Geändert hat sich inzwischen jedenfalls die Server-Angabe für LocalDB, die muss jetzt so heißen:

"Server=(LocalDB)\MSSQLLocalDB" (auf Groß- und Kleinschreibung kommt es hier allerdings nicht an).

Kann das das Problem sein?

Thema: CollectionViewSource.GetDefaultView oder besser new ListCollectionView?
Am im Forum: GUI: WPF und XAML

Zitat von LaTino
Bei solchen Fragen lohnt ein Blick in den Framework-Quellcode. GetDefaultView initialisiert das DataBinding, im Gegensatz zum einfachen Konstruktor.
Danke für den Hinweis, habe ich mir gleich mal lesegezeichnet (gelesezeichnet?).
Zitat von bb1898
Daneben finde ich sie praktisch für Master-Detail-Darstellungen: beim Wechsel des aktuellen Satzes in der Master-Liste soll die Detail-Liste mit den passenden neuen Sätzen gefüllt werden. Die ViewCollection kennt ihr CurrentItem und hat ein CurrentChanged-Ereignis, die zugrundeliegende Liste hat nichts dergleichen.

Während ich schreibe, kommt mir der Gedanke, dass sich das wahrscheinlich auch direkt im View regeln lässt, wird halt nur die Bindung der Detail-Liste etwas komplizierter. Muss ich mal ausprobieren.
Erfolgreich ausprobiert und ist in der Summe für ein halbwegs gewöhnliches Master-Detail-Szenario sogar eher kürzer, weil die diversen CurrentChanged-Handler entfallen.

Thema: CollectionViewSource.GetDefaultView oder besser new ListCollectionView?
Am im Forum: GUI: WPF und XAML

In einer bestimmten Situation, beschrieben in meinem ersten Beitrag, hat Variante 2 nicht funktioniert. In anderen Situationen schon. M.E. gehört der Unterschied der beiden Konstruktionen in der Dokumentation erklärt, aber ich finde nichts.

Thema: CollectionViewSource.GetDefaultView oder besser new ListCollectionView?
Am im Forum: GUI: WPF und XAML

Den Abschnitt aus der Dokumentation kenne ich schon, aber der besagt ja eigentlich nur, dass es die Default View immer gibt und wie man sie kriegt. Im Prinzip kann man aber beides tun: auf die automatisch erzeugte Default View-Komponente zugreifen und mit der arbeiten oder statt dessen mit einer eigens erzeugten CollectionView-Komponente arbeiten; der muss man natürlich die richtige Collection im Konstruktor übergeben. Oder hinterher die SourceCollection-Property setzen. Wenn ich mich nicht ganz falsch erinnere, wird die Default CollectionView nicht erzeugt, wenn man selbst eine erzeugt (so dass "es gibt sie immer" doch nicht ganz stimmt).

Wenn man mehrere Sortierungen oder Filter haben will, dann geht m.W. nur die Variante mit entsprechend vielen selbst erzeugten CollectionViews. Es finden sich andererseits auch Beispiele, in denen die Default View der ItemsSource einer Listbox o.ä. benutzt wird:


ListCollectionView view = CollectionViewSource.GetDefaultView(lstProducts.ItemsSource) as
    ListCollectionView;
Kommt aus einem nicht mehr ganz taufrischen WPF-Lehrbuch. Hat aber natürlich mit MVVM schon gar nichts zu tun. Bei so einem Verfahren ist die GetDefaultView-Variante mindestens naheliegend, wenn nicht zwingend, aber die Konstruktion ist eh nicht schön.

Thema: SQLite Update aktualisiert die Datenbank nicht und das Programm friert ein
Am im Forum: Datentechnologien

Da liegt wirklich ein Missverständnis vor: Tabellen- und Feldnamen einerseits, Feldwerte andererseits sind zwei Paar Stiefel und können nicht gleich behandelt werden. Tabellen- und Feldnamen sind im Normalfall ja Konstanten und können einfach in den SQL-Text geschrieben werden. Sind es doch Variable, hilft nur erst ordentlich überprüfen (falls vom Benutzer gekommen) und dann in den Text einbauen. Bei letzterem würde ich eher zu string.format("xxx {0}", variable) tendieren, aber das geht jetzt in die Geschmacksfragen.

Mit SQL-Parametern kann und soll man Feldwerte übergeben, sonst nichts. In Deinem funktionierenden Beispiel wird ja auch genau so verfahren.

Thema: CollectionViewSource.GetDefaultView oder besser new ListCollectionView?
Am im Forum: GUI: WPF und XAML

Zitat von feuerwehrmann
Kannst du etwas zu den Gründen sagen?
In allen Fällen in denen ich mit einer ViewCollection gearbeitet habe, hat mehr Probleme als Vorteile mit sich gebracht. Ich habe dann auch immer einen anderen Weg gefunden meine Probleme zu lösen.
Wichtigster Grund dürften wohl die Fälle sein, in denen die gleiche Liste unterschiedlich sortiert oder gefiltert angezeigt werden soll - also mehrere CollectionViews für eine Collection.

Daneben finde ich sie praktisch für Master-Detail-Darstellungen: beim Wechsel des aktuellen Satzes in der Master-Liste soll die Detail-Liste mit den passenden neuen Sätzen gefüllt werden. Die ViewCollection kennt ihr CurrentItem und hat ein CurrentChanged-Ereignis, die zugrundeliegende Liste hat nichts dergleichen.

Während ich schreibe, kommt mir der Gedanke, dass sich das wahrscheinlich auch direkt im View regeln lässt, wird halt nur die Bindung der Detail-Liste etwas komplizierter. Muss ich mal ausprobieren.

Dein Betrag ist jetzt allerdings der erste, den ich sehe, der vom Arbeiten mit CollectionViews dezidiert abrät, und das mit "mehr Probleme als Vorteile" kann ich aus eigener Kenntnis nicht bestätigen.

Meine ursprüngliche Frage bleibt offen.

Thema: [Access] Insert trägt nichts in die Datenbank ein trotz funktionierender Datenbankverbindung
Am im Forum: Datentechnologien

Zitat von Fitzel69
Ich werde jetzt mit den Parametern anfangen
Da tust Du auf jeden Fall mal gut daran, das brauchst Du nämlich für Datenbankanwendungen immer und dringend, ob nun mit Access oder mit einem anständigen Datenbanksystem.

Mein Lieblingszitat hierzu:
Zitat
Warning

Never, never, NEVER use Python string concatenation (+) or string parameters interpolation (%) to pass variables to a SQL query string. Not even at gunpoint.
Ersetze "Python string concatenation (+) or string parameters interpolation (%)" durch "String-Manipulationsmethoden Deiner bevorzugten Programmiersprache".

Thema: CollectionViewSource.GetDefaultView oder besser new ListCollectionView?
Am im Forum: GUI: WPF und XAML

Ausgangssituation: ich habe in meinen Daten irgendeine Sorte von Liste, will in einem WPF-Fenster daran binden und habe Gründe, im ViewModel nicht direkt auf die Liste zuzugreifen, sondern auf eine zugehörige CollectionView-Property.

Dann kann ich im Prinzip beides machen:

public class MainViewModel 
{
	public ObservableCollection<Zeugs> _meinKram;
	
	public ListCollectionView MeinKram { get; private set; }
	
	public MainViewModel()
	{
		_meinKram = HoleMeineDaten(irgend, welche, parameter);
		// Variante 1:
		MeinKram = (ListCollectionView)CollectionViewSource.GetDefaultView(
			_meinKram);
		// oder Variante 2:
		// MeinKram = new ListCollectionView(_meinKram);
		// ... alles, was sonst noch nötig ist
	}
	
	private ObservableCollection<Zeugs> HoleMeineDaten(parameter):
	{
		// irgendetwas
	}
	
	// ...
}
Ich finde im Netz und in meinen Lehrbüchern keine Erklärung zur Wahl zwischen beiden Varianten. Variante 1 scheint öfter benutzt zu werden, Variante 2 kommt aber auch vor.

Meine eigenen Experimente haben bisher eine Situation ergeben, in der nur Variante 1 das gewünschte Ergebnis hat - und da verstehe ich gar nicht, warum:

Die Daten enthalten eine Nachschlagetabelle, nennen wir sie Rubriken, und eine Datentabelle, nennen wir sie Dinge. Jedes Ding gehört zu genau einer Rubrik. In der Anwendung werden nur Dinge bearbeitet, hinzugefügt, evtl. gelöscht, die Rubriktabelle bleibt unverändert.

Ein Fenster hat drei Bereiche: Eine Listbox, einen Suchbereich, einen Detailbereich zur Bearbeitung eines Ding-Datensatzes.

Der Suchbereich hat u.a. eine Combobox zur Suche nach allen Dingen einer Rubrik. Der Detailbereich hat auch eine Combobox für Rubriken, hier soll dem einzelnen Satz die richtige Rubrik zugeordnet werden (diese Zuordnung kann sich schon mal ändern und muss jedenfalls für neue Sätze gesetzt werden). Die Listbox dient zur Anzeige des Suchergebnisses und zur Auswahl eines Satzes daraus.

Beide Comboboxen haben die gleiche ListCollectionView-Property des ViewModels als ItemsSource. In MainWindow.xaml sieht das so aus:

Im Suchbereich:
<ComboBox Grid.Row="1" Grid.Column="1" Margin="3"
		  ItemsSource="{Binding AlleRubrikenView}"
		  DisplayMemberPath="RubName"
		  IsSynchronizedWithCurrentItem="True" />

Im Detailbereich (das ist ein Grid, der den aktuellen Ding-Datensatz als eigenen DataContext hat):
<ComboBox Grid.Row="2" Grid.Column="1" Margin="3"
		  ItemsSource="{Binding DataContext.AlleRubrikenView, 
				RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
		  DisplayMemberPath="RubName"
		  SelectedValuePath="RubId"
		  SelectedValue="{Binding RubId}" />
Erzeuge ich AlleRubrikenView nach Variante 1 als DefaultView, dann verhält sich das Programm wie gewünscht, insbesondere sind die ausgewählten Rubriken in beiden Comboboxen unabhängig voneinander. Benutze ich Variante 2 und erzeuge AlleRubrikenView neu, dann ist in Suchbereich und Detailbereich immer die gleiche Rubrik ausgewählt, egal, in welchem Bereich ich eine Auswahl treffe. Das führt natürlich zu falschen Ergebnissen.

Ich würde es verstehen, wenn jede Bindung beider Comboboxen an die gleiche ListCollectionView-Property dazu führen würde, dass auch die Auswahl in beiden synchron verändert würde. Aber warum hängt das davon ab, wie die Property erzeugt wurde?

Und vor allem, wo wird das erklärt? In der offiziellen Dokumentation zu ListCollectionView und CollectionViewSource habe ich nichts gefunden, hier im Forum und auf StackOverflow auch nicht. Es ist natürlich immer möglich, dass ich einer Erklärung nicht alles entnommen habe, was drinsteckt. Oder aber falsch gesucht, und da kann mir hier vielleicht jemand mit weiteren Suchbegriffen unter die Arme greifen.

Thema: [Gelöst] Konsolenanwendung aus der IDE: Fenster dauerhaft anpassen?
Am im Forum: Entwicklungs- und Laufzeitumgebung (Infrastruktur)

Hat sich erledigt, Zufallsfund in der Python-Mailingliste: mir war nicht klar, dass der Punkt "Standardwerte" im Kontextmenü der Eingabeaufforderung nicht etwa alles auf Standardwerte zurücksetzt, sondern mir erlaubt, die Voreinstellungen nach meinen Wünschen zu ändern. Das wirkt dann auch auf die Fenster, die Visual Studio beim Debuggen einer Konsolenanwendung öffnet.

Thema: [Gelöst] Konsolenanwendung aus der IDE: Fenster dauerhaft anpassen?
Am im Forum: Entwicklungs- und Laufzeitumgebung (Infrastruktur)

Wenn ich aus Visual Studio heraus eine Konsolenanwendung starte, dann sieht das Fenster genau so aus wie gleich nach einer Windows-Installation: winzig, Schrift weiß auf schwarz und dünn. Auf meinem Desktop muss ich das zwar auch an vielen Stellen anpassen, das sind aber doch Einmalaktionen pro Installation.

Wenn ich zur Laufzeit der Anwendung, während sie auf eine Eingabe wartet, die Fenster-Eigenschaften ändere, dann überlebt diese Anpassung immerhin auch einen Neustart von Visual Studio, aber nur für dieses ganz bestimmte Projekt, nicht einmal für andere Projekte der gleichen Projektmappe.

Ich suche vergeblich nach einer Möglichkeit, die Eigenschaften des von der IDE gestarteten Konsolenfensters ein für alle Mal zu ändern. Unter "Extras / Optionen" kann ich nichts finden. In der Hilfe finde ich auch nichts, ich habe bei "Personalisieren der IDE" gesucht.

Kann mir jemand einen Tipp geben, wo ich die Information finden könnte?

Thema: Entity Framework - Überprüfen ob Datensatz in der Datenbank schon vorhanden ist
Am im Forum: Datentechnologien

Zitat von Abt
Zitat von Kingside
Müssen dafür Stored Procedures gebaut werden?
Nein, SP ist immer die aller aller aller aller letzte Lösung.
Wieso denn das?

Thema: Mit foreach- und for-Schleifen über ein Array iterieren
Am im Forum: Grundlagen von C#

Zitat von Lalgeriano
Zum Thema mit Xamarin. Mit deinem Tipp hat es geklappt! Über System.Text.StringBuilder lässt sich das Code ohne Probleme ausführen. Komisch, dass es anscheinend nicht korrekt referenziert wurde, so lange es aber so geht soll's mir egal sein :) Danke auch hierfür!
Der Namensraum System.Text ist nicht von allein eingebunden. Im Visual Studio enthält eine Klassendefinition normalerweise schon von Anfang an die entsprechende using-Anweisung im Kopf:

using System.Text;

Das scheint am Anfang Deiner Klassendatei nicht zu stehen und dann musst Du halt den StringBuilder komplett mit Namensraum ansprechen. Oder, natürlich, Du ergänzt die Anweisung.

Thema: Microsoft Visual Studio 2015 Denvenv: Schwerwiegender Fehler bei der Installation
Am im Forum: Entwicklungs- und Laufzeitumgebung (Infrastruktur)

Zitat von rangman135
Ich denke nicht das es sich jetzt schon auf mich auswirkt^^ aber besser schon mal auf dem aktuellen Stand sein als veraltet zu arbeiten^^

Ich habe die Iso gedownloadet, jedoch enthält sie zwei Ordner eine Datei und noch eine ".exe". Wie erstelle ich eine Iso? Ich habe nämlich die exe ausgeführt, gleiches Ergebnis...
Die Iso ist ja dafür gedacht, eine DVD daraus zu brennen. Wie das genau geht, hängt vom Brennprogramm ab. Bei Nero heißt es, wenn ich mich recht erinnere "Image brennen" o.ä.

Thema: WPF - TabItem wird nicht richtig aktualisiert
Am im Forum: GUI: WPF und XAML

Zitat von Lando
Deine DetailseiteVM-Klasse muss natürlich auch PropertyChanged auslösen, sonst bekommt die GUI die Änderung nicht mit.
Genauer gesagt, die Eigenschaft AktuellesBuch in dieser Klasse braucht einen Setter, in dem PropertyChanged ausgelöst wird. Damit funktioniert es jetzt.

Thema: WPF - TabItem wird nicht richtig aktualisiert
Am im Forum: GUI: WPF und XAML

Zitat von KroaX
Spontane Frage ohne alles 2x gelesen zu haben :

public ListenanzeigeVM Listen { get; private set; }
public DetailseiteVM Details { get; private set; }

Die werfen kein RaisePropertyChanged ... warum genau nicht ?
Die werden im Konstruktor gesetzt und dann nie mehr ausgetauscht. Wenn sich ihre Eigenschaften ändern, ist dafür ja RaisePropertyChanged bei diesen Eigenschaften zuständig.

Thema: WPF - TabItem wird nicht richtig aktualisiert
Am im Forum: GUI: WPF und XAML

Meine Testanwendung bewegt sich von einem Problem zum nächsten: Keine Fehlermeldungen im Entwurfsmodus mehr, aber beim Umschalten zwischen meinen beiden TabItems findet keine Aktualisierung statt. Genauer: sie wird im Fenster nicht sichtbar.

Das Hauptfenster hat das ViewModel KrimiVM; es enthält zwei UserControls: Listenanzeige und Detailseite. Listenanzeige hat ViewModel ListenanzeigeVM, Detailseite hat ViewModel DetailseiteVM.

KrimiVM hat

public ListenanzeigeVM Listen { get; private set; }
public DetailseiteVM Details { get; private set; }

public KrimiVM()
{
	Listen = new ListenanzeigeVM();
	Details = new DetailseiteVM();
}
Das bisschen "Geschäftslogik", das die Anwendung kennt, findet in der Klasse KrimiBAPI statt. Die implementiert INotifyPropertyChanged und hat unter anderem die Eigenschaft AktuellesBuch:


public BuchVM AktuellesBuch
{
	get
	{
		return _aktuellesbuch;
	}
	set
	{
		if (value != _aktuellesbuch)
		{
			_aktuellesbuch = value;
			RaiseChange("AktuellesBuch");
		}
	}
}

BuchVM ist eine triviale Verpackung der Modellklasse Buch und implementiert ebenfalls INPC.

Der Zugriff auf KrimiBAPI erfolgt über die Anwendungsklasse:

public partial class App : Application
{
	private static KrimiBAPI _bapi = new KrimiBAPI();

	public static KrimiBAPI BAPI
	{ get { return _bapi; } }
}
Die Klasse DetailseiteVM greift nur lesend auf App.BAPI.AktuellesBuch zu:

public BuchVM AktuellesBuch
{ get { return App.BAPI.AktuellesBuch; } }
In der Klasse ListenanzeigeVM wird diese Eigenschaft beim Wechsel des selektierten Listbox-Eintrags neu gesetzt.

Ein Ablaufverfolgungspunkt in der Loaded-Methode der Detailseite zeigt: Im ViewModel ist die Eigenschaft richtig, d.h. beim Wechsel in der Liste und anschließendem Umschalten auf die Detailseite enthält sie das ausgewählte Buch. Trotzdem zeigt die Detailseite immer das Buch an, das beim ersten Umschalten auf diese Seite aktuell war.

Die Detailseite besteht aus einem DataTemplate für die Buch-Details und zwei Schaltern:
<UserControl x:Class="OkCancel_Versuche.Detailseite"
    ... (xmlns wie üblich)
    xmlns:local="clr-namespace:OkCancel_Versuche"
    ...
    Loaded="UserControl_Loaded">
    <UserControl.Resources>
        <DataTemplate DataType="{x:Type local:BuchVM}">
            <Grid>
               ... (Details für ein Buch)
            </Grid>
        </DataTemplate>
    </UserControl.Resources>
    <DockPanel>
        <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
          ... (Schaltflächen)
        </StackPanel>
        <ContentControl Margin="5" Content="{Binding Path=AktuellesBuch}" />
    </DockPanel>
</UserControl>
Im Hauptfenster werden die UserControls so eingebunden:
<Window.Resources>
	<DataTemplate DataType="{x:Type local:ListenanzeigeVM}">
		<local:Listenanzeige />
	</DataTemplate>
	<DataTemplate DataType="{x:Type local:DetailseiteVM}">
		<local:Detailseite />
	</DataTemplate>
</Window.Resources>
<Grid>
	...
	<TabControl Grid.Row="1" SelectedIndex="{Binding AktuelleSeite}">
		<TabItem Header="Liste">
			<ContentControl Content="{Binding Listen}" />
		</TabItem>
		<TabItem Header="Details">
			<ContentControl Content="{Binding Details}" />
		</TabItem>
	</TabControl>
</Grid>
Ich vermute oder bin eigentlich fast sicher, dass ich mit dem DataTemplate in der Detailseite etwas falsch mache, bloß was?