[C#, Linq] EnumerableRowCollection<DataRow>, select, distinc

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Ik heb een form met 3 comboboxen: Plaats, Straat, Huisnummer. Ook heb ik een Excelsheet met die data.
Ik heb de Excel data ingelezen in een datatable, het gaat om veel data (350.000+ rijen). Het inlezen duurt dan ook wel een paar seconden ;)

Nu wil ik dat de gebruiker eerst een plaats selecteert, dan een straat en vervolgens een huisnr.
Ik wil mijn eerste combobox vullen met alle unieke plaatsnamen.
Na veel zoeken heb ik deze LINQ code bij elkaar gesprokkeld:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
string columnName = (string)frmLocationFinder.AttributeLabel1.Tag;
BindingSource bindingSource1 = new BindingSource();

this.frmLocationFinder.AttributeComboBox1.DataSource = bindingSource1;
this.frmLocationFinder.AttributeComboBox1.DisplayMember = columnName;

EnumerableRowCollection<DataRow> query = (
    from row in this.dt.AsEnumerable()
    orderby row.Field<String>(columnName)
    select row);

DataView view = query.AsDataView();

bindingSource1.DataSource = view;


Bovenstaande code geeft na enkele (6-8) seconden een combobox met alleen maar 'System.Data.DataRowView' als items.

Eigenlijk wil ik Distinct gebruiken:
C#:
1
2
3
4
5
EnumerableRowCollection<DataRow> query = (
  from row in this.fileContent.AsEnumerable()
  orderby row.Field<String>(columnName)
  select row.Field<String>(columnName))
  .Distinct<String>();

Maar dat kan zo niet: 'System.InvalidCastException: Kan een object van het type System.String niet converteren naar het type System.Data.DataRow.'

Als ik de query van het type EnumerableRowCollection<String> laat zijn, werkt mijn query.AsDataView() niet meer.

Iemand enig idee hoe ik een subselectie kan maken van mijn datatable en die als bindingsource voor mijn combobox kan gebruiken?

Acties:
  • 0 Henk 'm!

  • cfern
  • Registratie: Oktober 2009
  • Laatst online: 16-09 15:35
Als je Distinct() wilt gebruiken, moet je zelf een comparer schrijven die vertelt wanneer DataRows gelijk zijn aan elkaar.

Voorbeeld:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    class SingleFieldEqualityComparer<TField> : IEqualityComparer<DataRow>
    {

        private string column { get; set; }

        public SingleFieldEqualityComparer(string ColumnName)
        {
            column = ColumnName;
        }

        public bool Equals(DataRow x, DataRow y)
        {
            var value1 = x.Field<TField>(column);
            var value2 = y.Field<TField>(column);
            return x == y;
        }

        public int GetHashCode(DataRow obj)
        {
            return obj.GetHashCode();
        }
    }


gebruik:
code:
1
var DistinctRows = dt.AsEnumerable().Distinct(new SingleFieldEqualityComparer<string>(ColumnName));


Nu is DistinctRows wel een verzameling met rijen, i.t.t. een verzameling strings. Probeer deze eens als data source te gebruiken.

Lijkt wat veel werk misschien, maar deze custom comparer kun je wel mooi hergebruiken. :)

Disclaimer: deze code is wegens haast en vroeg nogal uit de losse pols. Garantie tot de deur.

"I'd rather have a full bottle in front of me, than a full frontal lobotomy." (Tom Waits) | PoE


Acties:
  • 0 Henk 'm!

Verwijderd

Een comparer schrijven is helemaal niet nodig, je kunt aan de distinct ook gewoon een Lambda expressie meegeven.

De AsDataView() method is volgens mij een extension method op een IEnumerable<DataRow> (zo uit m'n hoofd). Daarom werkt deze niet meer aangezien de Distinct() een IEnumerable<String> geeft.

Acties:
  • 0 Henk 'm!

  • cfern
  • Registratie: Oktober 2009
  • Laatst online: 16-09 15:35
Ik had graag iets als Distinct(dr => dr.Field<string>(columnName)) geschreven, maar een overload die een lambda accepteert bestaat helaas niet. Om eerlijk te zijn had ik zo'n overload eigenlijk wel verwacht.

Na enig gegoogle blijkt DistinctBy(..lambda..) wel te bestaan in een bibliotheekje dat extra LINQ methods toevoegt: morelinq

"I'd rather have a full bottle in front of me, than a full frontal lobotomy." (Tom Waits) | PoE


Acties:
  • 0 Henk 'm!

Verwijderd

Misschien ga ik de plank nu finaal misslaan maar zover ik nog weet uit mijn tijd met LINQ:

var s = (from u in userList select p.userType).Distinct();

dit geeft in S een lijst met alle userTypes. en hoewel dit over een generic list ging met een user-class, moet LINQ een DataRow niet anders benaderen. Dus uw query zou dan hetvolgende worden:

EnumerableRowCollection<DataRow> query = (
from row in this.dt.AsEnumerable()
orderby row.Field<String>(columnName)
select row).Distinct();

Acties:
  • 0 Henk 'm!

  • cfern
  • Registratie: Oktober 2009
  • Laatst online: 16-09 15:35
Die query geeft unieke rijen terug, gesorteerd op plaatsnaam. In het resultaat van die query kunnen echter nog wel meerdere rijen met dezelfde plaatsnaam voorkomen. Ik dacht dat het de bedoeling was om rijen terug te geven waarvan de plaatsnaam uniek is, vandaar dat ik zo moelijk deed met die overload van DistinctBy.

Nu ik echter de OP nog eens doorlees, was ik waarschijnlijk weer eens nodeloos ingewikkeld bezig. Als je namelijk alleen een gesorteerd rijtje unieke plaatsnamen wilt hebben, volstaat de volgende query:

C#:
1
2
3
4
5
var DistinctPlaces =
    dt.AsEnumerable()
      .Select(row => row.Field<string>(ColumnName))
      .OrderBy(place => place)
      .Distinct();


Dit levert een IEnumerable<string> op die weer gebruikt kan worden om een combobox te vullen.

"I'd rather have a full bottle in front of me, than a full frontal lobotomy." (Tom Waits) | PoE


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
@cfern: Het werkt prima.
Ik gebruik nu ook geen AsDataView meer maar vul de combobox met
C#:
1
distinctPlaces.ToArray<String>();

Ik ben nog druk bezig om LINQ en Lambda te leren en ik snap je OrderBy niet helemaal. Het werkt, maar waar komt place vandaan, het is niet iets in mijn dataset. Ik heb plaatsnaam als voorbeeld gebruikt maar in mijn huidige situatie gebruikt ik postcode en is plaatsnaam niet eens bekend.

Acties:
  • 0 Henk 'm!

  • cfern
  • Registratie: Oktober 2009
  • Laatst online: 16-09 15:35
Oei, ik weet niet of ik dit zo kort samen kan vatten, maar hier is een poging.

Een lambda expressie is eigenlijk een functie die je ter plekke definiëert. Dit zijn vaak eenvoudige functies en/of functies voor eenmalig gebruik, waarvan je eigenlijk geen zin hebt om ze apart te definiëren.

Bij het schrijven van zo'n lambda is het vaak wel handig om parameternamen te kiezen die iets duidelijk maken over het datatype dat in de parameter zit. Intellisense kan je dat ook wel vertellen, maar daar zou je niet op moeten vertrouwen. Ik dwaal af...

Wat wel belangrijk is hier, is dat de compiler kan afleiden welk datatype de lambda parameters hebben. Zoals je zelf zegt, je hebt deze parameters nergens vooraf gedeclareerd.

Als je nu de query eens regel voor regel bekijkt, doet deze het volgende:
C#:
1
var DistinctPlaces =

Hier laat ik de compiler het datatype van DistinctPlaces zelf afleiden d.m.v. var.
C#:
1
dt.AsEnumerable()

In deze stap wordt de datatabel een IEnumerable<DataRow>.
C#:
1
.Select(row => row.Field<string>(ColumnName)

Ah, de 1e anonieme functie. Hier gebruik ik Select om elk element van de lijst met DataRows in iets anders te veranderen. Ik gebruik hier een functie die de parameter 'row' (de compiler weet dat dit een DataRow is, omdat je in de vorige stap een lijstje met DataRows teruggaf) omzet in een veldwaarde die bij een gegeven kolom hoort. Ik mag de parameternaam zelf kiezen. Select (blaat => ..iets met blaat..) had ook gewerkt, maar het moge duidelijk zijn dat het gebruik van een goede naamgeving hier wel handig is.
C#:
1
.OrderBy(place => place)

Uit de vorige stap komt een IEnumerable<string>, nu wordt deze gesorteerd. LINQ kent geen methode .Order() zonder parameters, dus moet OrderBy gebruikt worden. Wat hier lijkt wat vaag, maar eigenlijk sorteer ik hier een lijst waarvan de ranking bepaald wordt door het element zelf. Ik moet dus een functie opgeven die zijn parameter onveranderd teruggeeft. Ook hier had ik best een andere naam voor de parameter mogen kiezen: OrderBy(x => x) had net zo goed gewerkt.

Mogelijk interessant: je zou ook dit kunnen doen: OrderBy(s => s.Length). Hier wordt niet op de string zelf geordend, maar op de lengte.
C#:
1
.Distinct()

Dit is de laatste, direct duidelijke stap. Deze geeft alleen elementen terug die nog niet eerder zijn gezien.

Tot zover de korte ( :X ) uitleg; ik hoop dat ik het niet vager heb gemaakt.

P.S.
Hier is de dezelfde code in query syntax, ter vergelijking.
C#:
1
2
3
4
5
var DistinctPlaces =
   (from row in dt.AsEnumerable()
    select row.Field<string>(ColumnName) into place
    orderby place
    select place).Distinct();

of iets korter, maar wel met (eigenlijk overbodige) herhaling van .Field<string>(...)
C#:
1
2
3
4
var DistinctPlaces2 =
   (from row in dt.AsEnumerable()
    orderby row.Field<string>(ColumnName)
    select row.Field<string>(ColumnName)).Distinct();

Ik gebruik zelf liever de lambda-syntax maar het is aan te raden om beiden te kunnen lezen/schrijven, omdat niet iedereen dezelfde voorkeur heeft.

P.P.S. Link met veel nuttige voorbeelden hier.

P.P.P.S (ok, laatste dan)
In plaats van ToArray<string>(), mag je ook ToArray() schrijven. De compiler kent het type van je IEnumerable namelijk al. :)

[ Voor 3% gewijzigd door cfern op 01-02-2010 09:17 ]

"I'd rather have a full bottle in front of me, than a full frontal lobotomy." (Tom Waits) | PoE


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
@cfern: Geweldig, enorm bedankt. Het is nu gelijk een stuk duidelijker.

Acties:
  • 0 Henk 'm!

  • DutchCommando
  • Registratie: November 2000
  • Laatst online: 22:42
Verwijderd schreef op zaterdag 30 januari 2010 @ 11:01:
Een comparer schrijven is helemaal niet nodig, je kunt aan de distinct ook gewoon een Lambda expressie meegeven.
cfern schreef op zaterdag 30 januari 2010 @ 17:26:
Ik had graag iets als Distinct(dr => dr.Field<string>(columnName)) geschreven, maar een overload die een lambda accepteert bestaat helaas niet. Om eerlijk te zijn had ik zo'n overload eigenlijk wel verwacht.
Niet zo heel raar. Gewoon een stukje information hiding. Stel je voor dat je een wijziging maakt aan de class waarover Distinct() wordt uitgevoerd: de lambda's die je op meerdere plaatsen in je code hebt staan zijn niet meer correct. Met een IEqualityComparer<T> zit je dan een stuk beter :).

[ Voor 3% gewijzigd door DutchCommando op 01-02-2010 18:51 ]


Acties:
  • 0 Henk 'm!

  • Grijze Vos
  • Registratie: December 2002
  • Laatst online: 28-02 22:17
DutchCommando schreef op maandag 01 februari 2010 @ 18:50:
[...]

Niet zo heel raar. Gewoon een stukje information hiding. Stel je voor dat je een wijziging maakt aan de class waarover Distinct() wordt uitgevoerd: de lambda's die je op meerdere plaatsen in je code hebt staan zijn niet meer correct. Met een IEqualityComparer<T> zit je dan een stuk beter :).
Hoezo? De lambda's zijn even goed getypeerd, en geven even goed een compile time error. Als je de velden aanpast die in de comparer of lambda gebruikt werden, moet je in beiden gevallen ze sowieso wijzigen.

Op zoek naar een nieuwe collega, .NET webdev, voornamelijk productontwikkeling. DM voor meer info

Pagina: 1