Laden...

EF, MySQL: Wie filtern?

Erstellt von 7.e.Q vor 13 Jahren Letzter Beitrag vor 13 Jahren 2.694 Views
7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 13 Jahren
EF, MySQL: Wie filtern?

verwendetes Datenbanksystem: MySQL (Connector.NET 6.3.6)

Hi Leute,

manchmal kriege ich echt 'ne Krise, wenn es um Anwendungen in C# geht. Heute: filtern von Datensätzen aus einer MySQL Datenbank, geholt über das Entity Framework.

In meinem WPF/XAML Window in den Resourcen lege ich mehrere CollectionViewSources an, die alle auf die selbe Source zeigen: eine Instanz eines Entity Models, das per Connector.NET 6.3.6 einen MySQL Server anspricht und von dort mehrere Tabellen holt.

Da ich diese Daten gern an diverse Steuerelemente wie unter anderem ein ListView binde, ist der CollectionViewType bei allen CollectionViewSources wohl BindingListCollectionView.

Ich möchte nun gerne aus einer Liste nur diejenigen Einträge im entsprechenden ListView angezeigt bekommen, die der Auswahl eines bestimmten Eintrags aus einer anderen Liste zugeordnet sind. Konkret heißt das folgendes:

Liste 1 ist eine Liste von Büchern. Liste 2 ist eine Liste von Autoren. Beide daran gebundenen ListViews sind IsSynchronizedWithCurrent=True. Ich möchte in Liste 1 nur die Bücher sehen, die von dem in Liste 2 ausgewählten Autor geschrieben wurden. Eine entsprechende Foreign Key Relation existiert sowohl in der MySQL Datenbank als auch automatisiert im EF-Model.

Normalerweise würde ich denken, dafür verwendet man die Eigenschaft Filter des entsprechenden Views. Pustekuchen, denn: CanFilter=False bei allen Views.

Dann dachte ich, okay, vielleicht geht's mit CustomFilter. Pustekuchen, denn: CanCustomFilter=False bei allen Views.

Also wie filtert man solche Daten dann, wenn die eigentlich dafür vorgesehenen Filtermechanismen beide aus mir absolut unerfindlichen nicht funktionieren? Und warum zum Geier funktionieren sie nicht? Was reitet die Entwickler bei Microsoft, solche essenziellen Features an zentraler Stelle einfach nicht zu unterstützen? 😜

Danke!

Grüße,
Hendrik

W
955 Beiträge seit 2010
vor 13 Jahren

Hi,

Warum navigierst Du nicht vom Autor zu seinen Büchern wenn die Relation im Modell modelliert wurde?

myAutor.Books o.ä.

7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 13 Jahren

Ja, das hab ich jetzt auch gemacht. So funktioniert das. Aber...

Ich erweitere das ganze aber jetzt mal für die Frage: was, wenn ich nach mehreren Kriterien filtern will? Nicht nur nach dem Autor, sondern beispielsweise auch nach einem bestimmten Titel oder nach einer bestimmten ISBN (Stichwort: Suchfunktion).

W
955 Beiträge seit 2010
vor 13 Jahren

Ich habe mit der CollectionView auch schon mal gearbeitet. Ich kann mich erinnern dass nicht alle Collectionen Filtering unterstützen. Ich hatte das damals mit einer ObservableCollection<T> als CollectionViewSource gelöst. Vllt damit mal versuchen.

7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 13 Jahren

Hmm, klingt gut. Wie würde das denn syntaktisch in XAML aussehen? Und kann die ObservableCollection so Sachen wie AddNew?

W
955 Beiträge seit 2010
vor 13 Jahren

Ich hatte das mit MVVM gemacht. Du kannst einfach ICollectionView an ItemsSource zum Binden verwenden:


        public ICollectionView Lieferanten 
        {
            
            get { return lieferanten; }
            set
            {
                if (lieferanten != value)
                {
                    
                    lieferanten = value;
                    lieferanten.Filter = FilterPredicate;
                    RaisePropertyChanged("Lieferanten");
                }
            }
        }

Wenn sich ein Filterkriterium ändert, dann entsprechend Refresh() aufrufen:


        public bool ShowIH
        {
            get { return showIH; }
            set
            {
                if (showIH != value)
                {
                    showIH = value;
                    RaisePropertyChanged("ShowIH");
                    lieferanten.Refresh();
                }
            }
        }

Von eingefügten oder gelöschten Entities bekommt der ObjektContext jedoch nichts mit. Ein Trick besteht darin eine EntityObservableCollection<T> zu erstellen und von ObservableCollection<T> zu erben. Diese bekommt eine Referenz zum ObjektContext und überlädt die entsprechenden Methoden unm diesen davon zu benachrichtigen:
(Quelle aus waf.codeplex.com)


    public class EntityObservableCollection<T> : ObservableCollection<T>
    {
        
        private readonly ObjectContext objectContext;
        private readonly string entitySetName;

        
        public EntityObservableCollection(ObjectContext objectContext, string entitySetName, IEnumerable<T> items) 
            : base(items ?? new T[] {})
        {
            if (objectContext == null) { throw new ArgumentNullException("objectContext"); }
            if (entitySetName == null) { throw new ArgumentNullException("entitySetName"); }

            this.objectContext = objectContext;
            this.entitySetName = entitySetName;
        }
         
        protected override void InsertItem(int index, T item)
        {
            base.InsertItem(index, item);

            objectContext.AddObject(entitySetName, item);
        }

        protected override void RemoveItem(int index)
        {
            T itemToDelete = this[index];
            base.RemoveItem(index);

            objectContext.DeleteObject(itemToDelete);
        }

        protected override void ClearItems()
        {
            T[] itemsToDelete = this.ToArray<T>();
            base.ClearItems();

            foreach (T item in itemsToDelete)
            {
                objectContext.DeleteObject(item);
            }
        }

        protected override void SetItem(int index, T item)
        {
            T itemToReplace = this[index];
            base.SetItem(index, item);

            objectContext.DeleteObject(itemToReplace);
            objectContext.AddObject(entitySetName, item);
        }

        public new void Add(T item)
        {

            base.Add(item);
            objectContext.AddObject(entitySetName, item);    
        }

        public new void Remove(T item)
        {

            base.Remove(item);
            objectContext.DeleteObject(item);    
        } 
    }

7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 13 Jahren

Hmm... ich weiß nicht, ob ich das verstehe.

Meine aktuelle Konstruktion sieht folgendermaßen aus. Im XAML des MainWindow in Window.Resources:


        <local:buecherdbEntities x:Key="BuecherDB" />

        <CollectionViewSource x:Key="BuecherView"  Source="{Binding Source={StaticResource BuecherDB}, Path='Buecher'}"    />
        <CollectionViewSource x:Key="AutorenView"  Source="{Binding Source={StaticResource BuecherDB}, Path='Autoren'}"    />
        <CollectionViewSource x:Key="VerlageView"  Source="{Binding Source={StaticResource BuecherDB}, Path='Verlage'}"    />
        <CollectionViewSource x:Key="ReihenView"   Source="{Binding Source={StaticResource BuecherDB}, Path='Reihen'}"     />
        <CollectionViewSource x:Key="BesitzerView"  Source="{Binding Source={StaticResource BuecherDB}, Path='Besitzer'}"  />

Ich binde dann die gewünschten Controls direkt an diese Resourcen. local:buecherdbEntities ist das Entity Model, welches direkt aus der MySQL Datenbank generiert wird.

Im CodeBehind mache ich bezüglich des Bindings nichts.

Sollte ich obige Resourcen lieber in den CodeBehind verlagern und die Bindung an die Controls dort vornehmen?

W
955 Beiträge seit 2010
vor 13 Jahren

Ich habe das alles im Code gemacht.


 <ListView SelectedItem="{Binding Path=Current, Mode=TwoWay}" 
                ItemsSource="{Binding Path=Lieferanten}">

Im Controller:


listViewModel.Lieferanten = CollectionViewSource.GetDefaultView(EntityController.Entities.Lieferanten);

Und EntityController.Entities.Lieferanten ist


new EntityObservableCollection<Lieferant>(context, "Lieferanten", context.Lieferanten);

Es werden also Entites aus der DB an EntityObservableCollection gebunden und als Source der CollectionView verwendet. Die DefaultView der CollectionView wird an die ItemsSource gebunden.
Das ViewModel enthält das Prädikat das zum Filtern verwendet wird:


        bool FilterPredicate(object item)
        {
      
            bool accepted;
            var lf = item as Lieferant;
            accepted = lf.isih;
            ...
            return accepted;
        }

Wenn die Liste neu zugewiesen oder mit Refresh() neu abgerufen wird dient das Prädikat als Filter. Und dieses kann ja anhand der Eigenschaften des VW entscheiden welche objekte in der Liste auftauchen sollen.
Ich hoffe dass das halbwegs verständlich war.

7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 13 Jahren

Hmm... hab mir das WAF jetzt mal angesehen. Grob. Interessant klingt es. Verwendest du das Managed Extensibility Framework für deine Anwendung? Oder wie hast du die aufgebaut und mit WAF verdrahtet?

W
955 Beiträge seit 2010
vor 13 Jahren

Hi,

ja ich nehme als IOC-Container das MEF, obwohl es strenggenommen keiner ist. Wenn man weiß wie es zu debuggen ist kann man sehr gut damit arbeiten.
WAF ist ein leichtgewichtiges MVVM-Framework und deshalb einsteigerfreundlich. Ich würde Dir empfehlen die Demos durchzuarbeiten, da wird einem mit steigender Schwierigkeit das "MVVM-Denken" nähergebracht. So habe ich es zumindest kapiert.

7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 13 Jahren

Ja, das Problem ist halt, dass ich das Ding schon nahezu fertig habe und eine Umstellung auf ein neues Konzept wohl dazu führen würde, dass ich das Teil komplett neu schreiben kann. Gut, ich meine, der Aufwand wäre jetzt nicht so groß.

Aber allein zu verstehen, wie man mit MEF und WAF umgeht, kostet 'ne Weile. Bisher blicke ich da noch nicht so ganz durch.