Avatar billede vivaa.dk Nybegynder
17. oktober 2007 - 11:20 Der er 20 kommentarer og
1 løsning

Søgning i c#

Hej

Jeg har en collectionklasse med en List<Bruger> med brugerdata.

Alle mine brugere bliver indlæst i et listview når jeg starter programmet op.

jeg vil gerne kunne søge i mine brugere ved at indgrænse på fx navn og tlf.

Jeg har lavet nogle tekstfelter og combobokse med forskellige ting jeg gerne vil kunne indgrænse min brugerliste.

Så når jeg fx skriver Mads i feltet "navn" er det kun dem der hedder Mads der bliver vist i mit listview. Og hvis jeg nedenunder indtaster postnr 5200, vil det kun være dem der hedder Mads og bor i postnr 5200 der bliver vist.

Søgningen skal ske løbene imens jeg taster i feltet. Dvs. onchanged eller hvad det nu hedder..

Hvordan laver jeg en sådan en søgning?
Avatar billede md_craig Nybegynder
17. oktober 2007 - 12:30 #1
Den hårde og tunge vej er jo at lave en søgemetode som su kører for hvert tastetryk...
Hvis du ikke har for mange navne vil dette performe ok... (sortering + binær søgning kan være en lille optimering oven på dette)....

Har du rigtig mange navne har jeg selv lavet et "søge træ"... hvordan man lige skal kombinere navn samt postnummer her kræver lidt tænkeri...

En ilustration kan ses her...

http://linux.thai.net/~thep/datrie/trie1.gif
forklaring: http://images.google.co.uk/imgres?imgurl=http://linux.thai.net/~thep/datrie/trie1.gif&imgrefurl=http://linux.thai.net/~thep/datrie/datrie.html&h=366&w=666&sz=6&hl=en&start=105&um=1&tbnid=7vxH9WPiBLDNWM:&tbnh=76&tbnw=138&prev=/images%3Fq%3Dsearch%2Btree%26start%3D100%26ndsp%3D20%26svnum%3D10%26um%3D1%26hl%3Den%26sa%3DN

Var lige det bedste jeg kunne finde...
Avatar billede md_craig Nybegynder
17. oktober 2007 - 12:31 #2
måske lidt bedre forkleret her: (har ikke læst nogen af dem)

http://en.wikipedia.org/wiki/Trie
Avatar billede vivaa.dk Nybegynder
17. oktober 2007 - 12:49 #3
Ville det være nemmere at lave det i et SQL kald? Så den kalder de data ind der bliver søgt på istedet?
Avatar billede md_craig Nybegynder
17. oktober 2007 - 14:20 #4
Det ville hvertfald ikke yde bedre.

og personlig set ville det heller ikke være nemmere da det skulle igennem mindst 2 lag (Domain og DataAccess)...

alternativt til at lave det optimale Trie eller et RadixTrei.. så kan du jo:
public List<Bruger> Search( string userSearch, string zipSearch )
{
List<Bruger> partialList = new List<Bruger>();
foreach( Bruger bruger in brugere )
{
  if( bruger.Name.StartsWith(userSearch) && bruger.ZipCode.ToString().StartsWith(zipSearch ) )
    partialList.Add( bruger );
}
return partialList;
}

(Det er ikke sikkert overstående kompilere, lige noget hurtig skrevet direkte her, men burde vise hvordan)...

Ulæmpen ved overstående er at den skal rende igennem hele listen hver gang brugeren laver et tastetryk, og på mange data kan det godt give en sløv respons tid...

Et Trie eller RadixTrei finde frem til de relevante resultater meget hurtigere... og kan desuden implementeres sådan at det husker ens position i træet... og bare flytter op hvis man fjerne en char, eller ned hvis man tilføjer en char...
Avatar billede md_craig Nybegynder
17. oktober 2007 - 14:24 #5
lidt om Radix Tree (eller Patricia trie)
http://en.wikipedia.org/wiki/Radix_tree
Avatar billede arne_v Ekspert
22. oktober 2007 - 00:16 #6
Search metoden burde nok laves med List<> FindAll og den jeg tror såmænd ikke at den
performer så dårligt - det tager ikke mange milliontedele sekunder at søge en
liste med nogle få hundrede entries igennem.
Avatar billede vivaa.dk Nybegynder
22. oktober 2007 - 09:24 #7
Det første lange stykke tid vil systemet nok kun indeholde omkring 200 brugere, men der vil være god mulighed for at det på et senere tidspunkt vil blive udvidet til 1000+ brugere.

Jeg har nu lavet systemet med en knap, så den ikke bare søger af sig selv, da jeg tror det bliver lidt for tungt.

Systemet skal også kodes op mod databasen vha. SOAP protokollen, når jeg engang får styr på dette, hvilket måske også vil betyde lidt dårligere performance.

Jeg har fået lavet et array af SQL statements så hvis man søger på de forskellige ting bliver der lige tilføjet et AND xx = 'xx' i sætningen ;) Måske en lidt primitiv løsning, men det fungerer faktisk ret godt.

Det andet der med Trie kan jeg ikke rigtigt overskue hvordan jeg skal stille op :S
Avatar billede md_craig Nybegynder
22. oktober 2007 - 10:18 #8
Det er nu ellers ikke så svært igen... men hvis vi snakker nede i de størelsesordner ville jeg ikke mene det er nødvendigt...

Men du kan jo implementere en Search metode lidt ala den jeg viser på din collectionklasse... så kan man jo sige den er inkapsuleret således at hvis du en dag står med så mange brugere at det begynder at blive tungt, kan det omimplementeres uden det store besvær.

Og så kan du naturligvis som Arne v bruge FindAll, den tager en delegate til en metode som skal returnere hvor vidt et element skal medtages i resultatet af FindAll kaldet...

Det er stadig hurtigere at søge direkte i ram istedet for at skulle ned i en database, men du skal igen op i mange flere brugere før det bliver et problem...
Avatar billede vivaa.dk Nybegynder
25. oktober 2007 - 18:57 #9
Har lavet det sådan nu:

            List<Kunde> resultat = kunder;

            if(sælger != 0)
                resultat = kunder.FindAll(delegate(Kunde k) { return k.Sælger1.Brugernr.Equals(sælger); });
            if(sælger2 != 0)
                resultat = kunder.FindAll(delegate(Kunde k) { return k.Sælger2.Brugernr.Equals(sælger2); });

            //if(branche != null)
            //    resultat = kunder.FindAll(delegate(Kunde k) { return k.Sælger2.Brugernr.Equals(sælger2); });

            if (kunde != null)
                resultat = kunder.FindAll(delegate(Kunde k) { return k.KundeNavn.ToLower().StartsWith(kunde.ToLower()); ; });
            if (postnr != 0)
                resultat = kunder.FindAll(delegate(Kunde k) { return k.Postnr.Equals(postnr); });
            if (cvr != 0)
                resultat = kunder.FindAll(delegate(Kunde k) { return k.Cvr.Equals(cvr); });

            return resultat;

Findall kører super fint :)

Det er en lidt anden klasse jeg roder i nu.. men stadig til samme spørgsmål.

Jeg har dog et lille problem..
Som i kan se er der en lille søgning der er udkommenteret, nemlig søgning på branche.

Jeg har en liste af brancher liggende på min kunde klasse.. Da en kunde godt kan være tilknyttet flere brancher i mit lille test system her :)

Hvordan får jeg implementeret søgning på branche i min nuværende søgning med findAll?
Avatar billede md_craig Nybegynder
26. oktober 2007 - 08:37 #10
ok den måde du lige bruger find all på ville jeg ikke anbefale til at starte med, benyt istedet en enkelt findall... for dit brance problem kan du bruge den der heder Exists istedet til at finde ud af om listen af brancher indeholder en given branche...

så noget ala:

            resultat = kunder.FindAll(delegate(Kunde k)
                {
                    bool result = false;
                    if (sælger != 0 && k.Sælger1.Brugernr.Equals(sælger))
                        result = true;

                    if (sælger2 != 0 && k.Sælger2.Brugernr.Equals(sælger2))
                        result = true;

                    if (branche != null && k.Brancher.Exists(delegate(Branche b) {
                      return b.Equals(branche); }))
                        result = true;

                    if (kunde != null && k.KundeNavn.ToLower().StartsWith(kunde, StringComparison.InvariantCultureIgnoreCase))
                        result = true;

                    if (postnr != 0 && k.Postnr.Equals(postnr))
                        result = true;

                    if (cvr != 0 && k.Cvr.Equals(cvr))
                        result = true;

                    return result;
                }
                );
Avatar billede md_craig Nybegynder
26. oktober 2007 - 08:41 #11
Tror lige jeg kom til at lave helt om i logiken der... dette skulle vist ramme bedre på det du har:

            resultat = kunder.FindAll(delegate(Kunde k)
                {
                    bool result = true;
                    if (sælger != 0 || !k.Sælger1.Brugernr.Equals(sælger))
                        result = false;

                    if (sælger2 != 0 || !k.Sælger2.Brugernr.Equals(sælger2))
                        result = false;

                    if (branche != null || !k.Brancher.Exists(delegate(Branche b) { return b.Equals(branche); }))
                        result = false;

                    if (kunde != null || !k.KundeNavn.StartsWith(kunde, StringComparison.InvariantCultureIgnoreCase))
                        result = false;

                    if (postnr != 0 || !k.Postnr.Equals(postnr))
                        result = false;

                    if (cvr != 0 || !k.Cvr.Equals(cvr))
                        result = false;
                    return result;
                }
                );
Avatar billede vivaa.dk Nybegynder
26. oktober 2007 - 08:57 #12
Ahh nice.. Takker :)

Synes godt nok også min egen var lige uoverskuelig nok.. :)
Avatar billede vivaa.dk Nybegynder
26. oktober 2007 - 09:01 #13
Hmm den går galt ved kundenavn..

Den skriver:

En værdi må ikke være null.
Parameternavn: value

Når jeg ikke udfylder den :S
Avatar billede vivaa.dk Nybegynder
26. oktober 2007 - 09:03 #14
Hvis den ikke finder nogle går den også ned fordi den så returnerer null, hvilket den ikke kan finde ud af at adde til listen :S
Avatar billede vivaa.dk Nybegynder
26. oktober 2007 - 09:06 #15
Ahh den kan ikke finde ud at at adde til listen fordi den returnerer en bool og min ListView er sat til at trække data fra den List<Kunde> som før..
Avatar billede vivaa.dk Nybegynder
26. oktober 2007 - 09:22 #16
Må indrømme at jeg ikke helt forstår din kode..

Alle kombinationsmuligheder skal være mulige.. Fx hvis man vil søge på sælger1 og postnr, så den finde alle de kunder der har den søgte sælger1 og postnr.

Og hvorfor returnerer den en bool, når det er søgeresultater jeg skal trække?
Avatar billede vivaa.dk Nybegynder
26. oktober 2007 - 09:39 #17
Har stillet det op sådan:

        public List<Kunde> søgKunde(int sælger, int sælger2, string branche, string kunde, int postnr, int cvr)
        {
            List<Kunde> resultat = kunder;

            resultat = kunder.FindAll(delegate(Kunde k)
            {
                bool result = true;
                if (sælger != 0 || !k.Sælger1.Brugernr.Equals(sælger))
                    result = false;

                if (sælger2 != 0 || !k.Sælger2.Brugernr.Equals(sælger2))
                    result = false;

                if (branche != null || !k.Brancher.Exists(delegate(Branche b) { return b.Equals(branche); }))
                    result = false;

                if (kunde != null || !k.KundeNavn.StartsWith(kunde, StringComparison.InvariantCultureIgnoreCase))
                    result = false;

                if (postnr != 0 || !k.Postnr.Equals(postnr))
                    result = false;

                if (cvr != 0 || !k.Cvr.Equals(cvr))
                    result = false;

                return result;
            });

            return resultat;
        }

Hvis jeg ikke skriver noget i kundenavn går den ned, fordi værdien ikke må være null.
Og hvis jeg skriver noget i kundenavn som jeg ved eksistere bliver der alligevel ikke added noget til listen :S
Avatar billede md_craig Nybegynder
26. oktober 2007 - 10:32 #18
Ja jeg blev forviret over det du gør i den anden... den er nemlig lige lidt underlig måde du har stillet den anden op på, og var derfor ikke helt klar over hvordan du ville udfører den...

Principet er at du i den blok her:
            {
                bool result = true;
                if (sælger != 0 || !k.Sælger1.Brugernr.Equals(sælger))
                    result = false;

                if (sælger2 != 0 || !k.Sælger2.Brugernr.Equals(sælger2))
                    result = false;

                if (branche != null || !k.Brancher.Exists(delegate(Branche b) { return b.Equals(branche); }))
                    result = false;

                if (kunde != null || !k.KundeNavn.StartsWith(kunde, StringComparison.InvariantCultureIgnoreCase))
                    result = false;

                if (postnr != 0 || !k.Postnr.Equals(postnr))
                    result = false;

                if (cvr != 0 || !k.Cvr.Equals(cvr))
                    result = false;

                return result;
            }

Returnere true hvis et kundeobject skal medtages ud fra de kreterier der er... (som jeg har sat det op er det krævet at alle er opfyldt hvis der er noget i dem, for sådan ville jeg have troet det skulle være)...

og du skal nok lige rettet all || til &&... var fordi jeg havde den anden og fik Resharper til at inverte if'sne... så prøv evt med:

            resultat = kunder.FindAll(delegate(Kunde k)
            {
                if (sælger != 0 && !k.Sælger1.Brugernr.Equals(sælger))
                    return false;

                if (sælger2 != 0 && !k.Sælger2.Brugernr.Equals(sælger2))
                    return false;

                if (branche != null && !k.Brancher.Exists(delegate(Branche b) { return b.Equals(branche); }))
                    return false;

                if (kunde != null && !k.KundeNavn.StartsWith(kunde, StringComparison.InvariantCultureIgnoreCase))
                    return false;

                if (postnr != 0 && !k.Postnr.Equals(postnr))
                    return false;

                if (cvr != 0 && !k.Cvr.Equals(cvr))
                    return false;

                return true;
            });
Avatar billede md_craig Nybegynder
26. oktober 2007 - 10:34 #19
og svar på:

Og hvorfor returnerer den en bool, når det er søgeresultater jeg skal trække?

Det er fordi det er en anonym metode der står for at vurdere hvor vidt en kunde skal med eller ej, det er FindAll metoden som opretter en liste med alle funde resultater og returnere den.
Avatar billede vivaa.dk Nybegynder
26. oktober 2007 - 11:23 #20
Nice.. Så virker det :)

Smid svar for points.. :)
Avatar billede md_craig Nybegynder
26. oktober 2007 - 12:26 #21
ök
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
IT-kurser om Microsoft 365, sikkerhed, personlig vækst, udvikling, digital markedsføring, grafisk design, SAP og forretningsanalyse.

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester