Avatar billede nielslbeck Nybegynder
22. februar 2006 - 20:50 Der er 11 kommentarer og
2 løsninger

Opdatering af DataTable "on-the-fly"

Jeg har følgende:

<asp:GridView runat="server" ID="gv1" DataSourceID="sdsStuff" AutoGenerateColumns="false" />
<asp:SqlDataSource runat="server" ID="sdsStuff" ConnectionString="..." SelectCommand="SELECT Month FROM Months" />

I codebehind-filen tilføjer jeg columns således:

TestField field = new TestField();
field.HeaderText = "Måned";
field.DataField = "Month";
gv1.Columns.Add(field);

TestField er kodet på følgende vis:

public class TestField : BoundField {

    public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex) {
        base.InitializeCell(cell, cellType, rowState, rowIndex);

        if (rowIndex == 2)
            cell.DataBinding += new EventHandler(cell_DataBinding);
    }

    void cell_DataBinding(object sender, EventArgs e) {
        DataControlFieldCell cell = (DataControlFieldCell)sender;
        GridViewRow row = (GridViewRow)cell.NamingContainer;
        DataRowView dataItem = (DataRowView)row.DataItem;

        DataRow dataRow = dataItem.Row.Table.NewRow();
        dataRow[0] = -1;
        dataItem.Row.Table.Rows.InsertAt(dataRow, 3);
    }

}

Da SqlDataSourcen returnerer månederne 1,2,3,4,5 ville jeg nu forvente følgende output i gridviewet:

1
2
3
-1
4
5

Men jeg får i stedet følgende:

1
2
3
-1
4

Dette skyldes, at vi kun løber de 5 rækker igennem, som fra starten var i datasourcen - og ikke de 6, som nu er der.

Spørgsmålet er nu, hvordan jeg får den sidste række med i outputtet?

Og lige et tillægsspørgsmål: Nogen der kan indsætte en ekstra række på en lidt kønnere måde, end jeg har i ovenstående? Synes der er lidt mange casts :-(
Avatar billede dr_chaos Nybegynder
22. februar 2006 - 20:58 #1
kan du ikke bare benytte BoundField b = new BoundField (); ?
Avatar billede nielslbeck Nybegynder
22. februar 2006 - 21:05 #2
Nej, det kan jeg ikke. Jeg vil gerne tilføje rækker til den underliggende DataTable on-the-fly :-) Hvis det ikke kan lade sig gøre on-the-fly, kan jeg evt. blive nødt til at løbe DataTablen igennem før jeg starter på udskrivningen - og så vil jeg gerne vide, hvordan jeg gør det :-)

Men svaret er i hvert fald, at jeg ikke kan udnytte nogen af de indbyggede Field-kontroller udenvidere...
Avatar billede snepnet Nybegynder
25. februar 2006 - 10:46 #3
jeg er ikke helt sikker på at jeg forstår hvad du vil frem til ... umiddelbart virker det meget mærkeligt at dit boundfield piller ved datakilden på den måde - i det hele taget specielt at ændre i datakilden efter databinding er påbegyndt - og før det er afsluttet.

hvis du skal manipulere med data på den måde - vil jeg forslå dig at lave et forretningslag, og lægge det der - og så databinde mod det istedet.

mvh
Avatar billede nielslbeck Nybegynder
25. februar 2006 - 12:30 #4
Okay, det jeg gerne vil opnå er følgende:

Jeg vil gerne have en MonthField, som udskriver månedesnavne i tabellen. Det vil sige, at jeg gerne vil have noget output i stil med:

Januar
Februar
Marts
April

Problemet er nu, at de data som jeg databinder op mod, måske kun indeholder:

Januar
Februar
April

Her mangler altså marts :-( Jeg vil lave ændringen i forretningslaget, da det jo ikke er en ændring som på nogen måde skal foregå i dataene som sådan, men blot foregå i de data, som brugeren ser. På den anden side synes jeg ikke det er så smart selv at rette i den tabel, som gridviewet genererer - det har jeg forsøgt. Det giver lidt problemer med hensyn til alternateitems, da gridviewet ikke får at vide, at der er indsat ekstra rækker.

Desuden skulle denne MonthField gerne være en field, som jeg kunne smide på et hvilkent som helst gridview hvor jeg ønsker at udskrive månedsnavne - og på den måde helt automatisk få alle måneder med, uden først at skulle røre ved mine data, som jeg henter direkte via en SqlDataSource.

På denne måde ville jeg heller ikke først skulle løbe hele datatabellen igennem og indsætte manglende måneder, men blot gøre det on-the-fly, hvilken naturligvis ville spare et gennemløb :-)

Håber I forstår - og har en løsning :-)
Avatar billede snepnet Nybegynder
25. februar 2006 - 13:12 #5
hej igen.

jeg forstå ikke hvorfor du vil databinde imod det der er i databasen, hvis det ikke er det du vil have vist?
hvis du blot vil vise alle månederne - kan du bare lave en liste med dem, og så databinde mod den istedet - eller snup dem herfra:
System.Globalization.DateTimeFormatInfo.CurrentInfo.MonthNames

men som skrevet - hvis du gerne vil arbejde med nogle lister der delvist kommer fra en database (eller anden ressource) - så synes jeg du skal arbejde med en objectdatasource istedet, og lave dig et forretningslag der håndterer det med at samle data flere steder fra.

mvh
Avatar billede nielslbeck Nybegynder
25. februar 2006 - 13:51 #6
Det er ikke kun månedsnavnene jeg vil have vist - det var bare for, at gøre eksemplet så simpelt som muligt :-) Det er en stor mængde handelsstatistik der skal hentes ud fra databasen og vises til brugeren. Men for nogle måneder kan det godt tænkes, at der ikke er nogen data, som hentes ud. Jeg vil dog alligevel gerne vise disse måneder til brugeren, så han ikke selv skal opdage, at der mangler en eller flere måneder.

Jeg vil gerne undgå at gøre min SQL endnu mere kompleks, ved også at skulle hente data ud for måneder, hvor der sådan set ikke er nogen data. Desuden ville der heller ikke være nogen grund til, at sende række efter række med 0'er fra databasen til ASP.NET serveren. Men ude hos brugeren er det ret vigtigt, at alle måneder er med - ellers er det også ret svært at beregne gennemsnittet, summen osv for den valgte periode, hvilket jeg gør gennem en ComputedField jeg har lavet.

Jeg har allerede implementeret det hele - det eneste der mangler er sådan set, at gridviewet udskriver alle dataene fra datatabellen, og ikke kun det antal rækker der var der til at starte med.
Avatar billede snepnet Nybegynder
25. februar 2006 - 14:53 #7
well... ok så....
det nemmeste nok (hvis du vil holde fast på din sqldatasource), at du sikrer dig - at du altid får en tilstrækkelig datamængde når data hentes, og at det er præsentationskontrollen der håndterer det.

jeg vil foreslå at du specialiserer en repeater, og sørger for at dens getdata sikrer datamængden.... noget i denne stil (det er bare en idéskitse):

namespace UIControls
{
    public class SpecializedRepeater : Repeater
    {       
        protected override IEnumerable GetData()
        {
            // hent view
            DataView data = base.GetData() as DataView;
            Debug.Assert(data != null);

            // den maksimale værdimængde (dine måneder)
            string[] allValues = new string[] { "anders", "dennis", "ursula", "ukendt" };           

            // den mængde du har hentet i basen
            List<string> valuesFromDatabase = new List<string>();
           
            // iteration over dine data (spurgte du vist også til)
            foreach (DataRowView rowView in data)
            {
                string name = rowView["Name"].ToString();
                if (!valuesFromDatabase.Contains(name))
                {
                    valuesFromDatabase.Add(name);
                }
            }

            // det der skal tilføjes
            List<string> valuesToAdd = new List<string>();
            foreach (string value in allValues)
            {
                if (!valuesFromDatabase.Contains(value))
                {
                    valuesToAdd.Add(value);
                }
            }

            // og dem kan du så tilføje
            foreach (string newValue in valuesToAdd)
            {
                // opret ny række
                DataRow row = data.Table.NewRow();
                row["Name"] = newValue;
                data.Table.Rows.Add(row);
            }

            // returnér
            return data as IEnumerable;
        }
    }
}

.... jeg formoder at brugerene ikke har mulighed for at lave opdateringer i bemeldte ui.

mvh
Avatar billede nielslbeck Nybegynder
25. februar 2006 - 17:51 #8
Jeg vil som sagt meget gerne benytte et GridView, da jeg på den måde kan lave det mix af BoundFields, CommandField, ComputedField osv, som den givne tabel kræver. Med en repeater får jeg en masse ekstra arbejde i og med, at jeg skal lave ItemTemplates for hver eneste tabel jeg behøver.

Meningen med MonthField er jo, at jeg kan tage et hvilkent som helst GridView og smide den i - og vupti! Ikke nok med, at der så står månedsnavne - næh, der er også automatisk indsat de måneder, der ellers ikke indeholdt nogen data :-D Det ville da være smart!

GridView er generelt klart at foretrække fremfor Repeater, da den klarer en masse kedeligt arbejde helt selv, og generelt kræver meget mindre kodning :-) Men der kan naturligvis være situationer, hvor den ikke giver den fornødne fleksibilitet. Men jeg vil ikke mene, dette skulle være en sådan situation - det kræver bare lidt tricks :-)

Det undrer mig faktisk lidt, at jeg kan ændre i den underliggende datatabel, da GridView der i dokumentationen for IEnumerator står:

An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and the next call to MoveNext or Reset throws an InvalidOperationException.

Så det skulle jo faktisk slet ikke kunne lade sig gøre, det jeg gør - og dermed burde jeg heller ikke gøre det. Men jeg synes alligevel det er den smarteste måde at gøre det på (hvis ellers jeg får det til at virke). Internt bruger GridViewet en PagedDataSource når den løber elementerne i datasourcen igennem - og det er den, der kun gider løbe op til antallet af elementer, som er i datasourcen til at begynde med.

Når GridViewet siger GetEnumerator på sin PagedDataSource, sker der følgende:

public IEnumerator GetEnumerator() {
    int start = this.FirstIndexInPage;
    int upperBound = -1;
    if (this.dataSource is ICollection)
        upperBound = this.Count;
    if (this.dataSource is IList)
        return new PagedDataSource.EnumeratorOnIList((IList)this.dataSource, start, upperBound);
    if (this.dataSource is Array)
        return new PagedDataSource.EnumeratorOnArray((object[])this.dataSource, start, upperBound);
    if (this.dataSource is ICollection)
        return new PagedDataSource.EnumeratorOnICollection((ICollection)this.dataSource, start, upperBound);
    if (!this.allowCustomPaging && !this.allowServerPaging)
        return this.dataSource.GetEnumerator();
    return new PagedDataSource.EnumeratorOnIEnumerator(this.dataSource.GetEnumerator(), this.Count);
}

Så hvis jeg måske lavede min egen datasource, som blot var en udvidelse af SqlDataSource og som hoppede i linien "return this.dataSource.GetEnumerator();", kunne jeg selv bestemme, hvor mange rækker der skulle udskrives - right?

Det ville jeg kunne acceptere som en løsning :-)
Avatar billede snepnet Nybegynder
26. februar 2006 - 00:28 #9
hmm... ved ikke helt hvorfor jeg havde fået den idé at du arbejdede med en repeater - det står jo meget eksplicit at det ikke er tilfældet... det må være fra et andet spørgsmål ... sorry - er nok ved at blive gammel...

du kan godt specialisere datasources og deres respektive views.... du kan se en implementering her:
http://www.nikhilk.net/Category.ASPNET.aspx
(det er en artikelserie ... DataSourceControls part 1-5 + summary).

jeg er ikke vild med løsningen hvor en kolonnetype udvider en specifik datakilde.... det vil virke meget uigennemskueligt udefra vil jeg tro, og det synes ikke at være en generel kolonne, der kan benyttes i ethvert grid.... desuden er jeg usikker på om det overhovedet vil kunne lade sig gøre (inden for anstændighedens rammer ;o).

det er korrekt at der er versionering på enumerators, men der er forskellige måder at implementere det på - bl.a. ved brug af kopiering.
du kan f.eks. uden problemer bruge en enumerator på et dataview - og i løkken ændre ting og sager i tabellen du har lavet dit view ud fra (og en foreach vil blive rullet igennem udfra det antal rækker der var i tabellen da du hentede enumeratoren) -
hvorimod du ikke vil kunne iterere via en enumerator over rækkerne i selve tabellen, og ændre tabellens rækker i løkken.

jeg kan ikke rigtig se hvordan du skulle komme igennem ved at bryde ind i den kode du har vist... det er implementeringen af GetEnumerator på PagedDataSource, men sådan en bruger du jo ikke. du bruger en sqldatasource, som internt arbejder med et SqlDataSourceView.
hvis du vil bryde ind i det med det du ønsker der skal ske - vil jeg tro at du bliver nødt til at specialisere både sqldatasource (så du får returneret dit eget view) + sqldatasourceview, hvor du så overskriver PerformSelect, men det er meget kode du skal skrive, hvis det skal virke som det plejer - det er en metode der gør lidt af hvert, baseret på diverse cachingforhold etc.
AccessDataSource og AccessDataSourceView er specialiseringer af de tilsvarende sql-klasser, så du kan se eksempler på det der (men jeg tror ikke du vil finde den kode du har brug for der).

jeg synes fortsat du skal lave dig en lille klasse der håndterer problematikken for dig - og så databinde til den istedet vha. en objectdatasource.... så længe du bare skal hente data (og ikke opdatere) er det ikke så meget der skal laves.

alternativt synes jeg du skal droppe din sqldatasource og skrive de kodelinier der skal til for at hente data - og databinde "manuelt".... det vil formentlig være det letteste... så suger du bare data op i en tabel, og tilføjer de rækker du vil før du databinder.

mvh
Avatar billede nielslbeck Nybegynder
28. februar 2006 - 09:05 #10
Hej igen - undskyld jeg ikke har fået svaret tilbage noget før, men jeg har haft travlt med så meget andet :-( Men nu er jeg her - og har fået løst problemet :-)

Grunden til, at jeg begyndte at snakke om PagedDataSource er, at GridViewet bruger sådan en internt. Så selvom jeg ikke bruger den explicit, bruger jeg den altså alligevel, da jeg benytter et GridView. Så det var faktisk GetEnumerator i ovenstående, der gav mig min løsning.

Løsningen på problemet blev, at jeg lavede min egen datasource, som blot er en udvidelse af SqlDataSource. Den er i grunden blevet rimelig simpel, så den gør det, at den overskriver GetView, som i stedet for at returnere et SqlDataSourceView returnerer mit eget view. Det returnerede view er igen blot en mindre udvidelse af SqlDataSourceView, som når ExecuteSelect kaldes returnerer sin egen IEnumerable, hvor MoveNext ser således ud:

public bool MoveNext() {
    index++;
    return index < dataView.Count;
}

Her er forskellen på denne enumerator og den der benyttes som standard, at min enumerator løber helt op til det antal rækker der er i dataviewet, og ikke blot til det antal rækker der var, da enumeratoren blev initialiseret. Så når jeg tilføjer rækker via min MonthField, bliver de sidste rækker i dataviewet stadig udskrevet :-)

Jeg kan godt se hvad du mener når du siger, at det kan virke lidt uigennemskueligt, når en Field i et GridView pludselig begynder at ændre på den underliggende datasource. Men min kode er godt kommenteret og efterhånden (efter lidt oprydning) blevet rimelig simpel. Så jeg håber mine efterfølgere på jobbet også kan finde rundt i den - selvom der ikke umiddelbart skulle være grund til ændringer :-) Jeg kan heller ikke lige overskue, om den vil virke i ethvert grid, men til de formål jeg skal bruge den til virker den perfekt! Jeg behøver slet ikke selv bekymre mig om, hvilke måneder jeg har data for. Jeg fortæller blot min MonthField den første måned jeg jeg vil have data for og den sidste måned jeg vil have data for. Så sørger den selv for at indsætte mellemliggende måneder, udskrive årstal osv.

Smider du lige et svar - så skal du vist også ha' lidt points, for ikke at have ladet mig sidde alene med dette spørgsmål :-) Og selvom jeg ikke brugte din løsning med en objectdatasource, har jeg fået meget godt og uddybende input fra dig!
Avatar billede snepnet Nybegynder
28. februar 2006 - 10:42 #11
jamen velbekomme da :o)
mvh
Avatar billede snepnet Nybegynder
28. februar 2006 - 10:59 #12
og undskyld det med pageddatasource - jeg troede et øjeblik at du sidestillende en datasourcecontrol med en pageddatasource.
mvh
Avatar billede nielslbeck Nybegynder
28. februar 2006 - 11:01 #13
Tak for hjælpen :-)
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