PDA

View Full Version : [C# / Linq To Entities]Problema con duplicati


RaouL_BennetH
23-04-2012, 19:01
Buonasera a tutti.

Vorrei capire come rimuovere dei duplicati da una "query":



using(GammaEntities context = new GammaEntities())
{
var result = context.CustomView.Where
(
x.SomeValue == "BLA"
&& x.OtherValue != "BOB"
)

aBinding.BindingSource = result.OrderBy(x => x.aField);
}


In linea di massima ottengo tutti i risultati corretti eccetto un paio che mi
vengono restituiti duplicati.
Per duplicato intendo che è la stessa identica riga con gli stessi identici valori.

Grazie a tutti.

RaouL.

=KaTaKliSm4=
23-04-2012, 19:19
Buonasera a tutti.

Vorrei capire come rimuovere dei duplicati da una "query":



using(GammaEntities context = new GammaEntities())
{
var result = context.CustomView.Where
(
x.SomeValue == "BLA"
&& x.OtherValue != "BOB"
)

aBinding.BindingSource = result.OrderBy(x => x.aField);
}


In linea di massima ottengo tutti i risultati corretti eccetto un paio che mi
vengono restituiti duplicati.
Per duplicato intendo che è la stessa identica riga con gli stessi identici valori.

Grazie a tutti.

RaouL.

C'è un problema di fondo, se la query ritorna dati duplicati è perchè il database non è normalizzato, la tabella non ha chiavi primarie giusto?

RaouL_BennetH
23-04-2012, 19:23
C'è un problema di fondo, se la query ritorna dati duplicati è perchè il database non è normalizzato, la tabella non ha chiavi primarie giusto?

Centro :)

Ma su questo purtroppo non abbiamo modo di agire. Il database in questione è solo una replica (su sql server 2008) di un db proprietario.

=KaTaKliSm4=
23-04-2012, 19:32
Centro :)

Ma su questo purtroppo non abbiamo modo di agire. Il database in questione è solo una replica (su sql server 2008) di un db proprietario.

Per caso è la replica di un gestionale?Per curiosità potresti farmi capire quale?Lavoro parecchio con le repliche DB, magari ho già lavorato con il DB in questione...

Comunque dovresti scriverti una classe comparer (eredita da IEqualityComparer) :

class TuoRecordComparer : IEqualityComparer<TuoRecord> {

public bool Equals(TuaEntità x, TuaEntità y) {
return x.CampoA == y.CampoA && x.CampoB == y.CampoB && Etc... ;
}

public int GetHashCode(TuaEntità obj) {
return obj.CampoA.GetHashCode() * obj.CampoB.GetHashCode();
}
}

Ed utilizzarlo come argomento nel metodo Distinct :

List<TuaEntità> res = TuaCollection.Distinct(new TuoRecordComparer());

Fammi sapere :)

RaouL_BennetH
23-04-2012, 19:41
per la "replica" hai pvt :)

Ora provo ad implementare il tuo suggerimento.

Kralizek
23-04-2012, 19:49
un semplice distinct alla fine?


using(GammaEntities context = new GammaEntities())
{
var result = context.CustomView.Where
(
x.SomeValue == "BLA"
&& x.OtherValue != "BOB"
)

aBinding.BindingSource = result.OrderBy(x => x.aField).Distinct();
}


anche perché la classe che implementa IEqualityComparer<T> non verrebbe vista da EF forzandoti ad eseguire l'operazione in memory con L2Object invece che sul db

RaouL_BennetH
23-04-2012, 20:00
un semplice distinct alla fine?


using(GammaEntities context = new GammaEntities())
{
var result = context.CustomView.Where
(
x.SomeValue == "BLA"
&& x.OtherValue != "BOB"
)

aBinding.BindingSource = result.OrderBy(x => x.aField).Distinct();
}


anche perché la classe che implementa IEqualityComparer<T> non verrebbe vista da EF forzandoti ad eseguire l'operazione in memory con L2Object invece che sul db

Ciao :)

Avevo già provato con la distinct ma senza successo.

E ho provato anche a spostarlo "all'inizio"


var result = context.Bla.Distinct().Where(blablabla);

=KaTaKliSm4=
23-04-2012, 20:01
un semplice distinct alla fine?


using(GammaEntities context = new GammaEntities())
{
var result = context.CustomView.Where
(
x.SomeValue == "BLA"
&& x.OtherValue != "BOB"
)

aBinding.BindingSource = result.OrderBy(x => x.aField).Distinct();
}


Non "dovrebbe" funzionare a rigor di logica, senza implementare un comparer, semplicemente perchè l'entità non ha chiavi primarie e non è possibile distinguere un oggetto da un altro.

E' vero, con il comparer l'operazione viene effettuata in memoria, ma non mi sembra un gran danno, ipotizando il numero di record.

L'unico modo per effettuare lo scarto dei duplicati direttamente sul DB è utilizzare una GroupBy a questo punto...

var Res = TuaCollection.GroupBy(o => o.CampoA).Select(g => g.First());

Kralizek
23-04-2012, 21:26
Ma il distinct in SQL mica richiede la presenza di una chiave primaria...

Potresti postare la query generata da EF?

In alternativa potrebbe fare una projection dell'entity in un tipo anonimo, eseguire il distinct e convertire gli oggetti restituiti in entity.

Ovviamente in questo modo si perde il tracking automatico delle modifiche, ma quello era già buono ed andato con il comparer in memory

gugoXX
24-04-2012, 09:39
Il Distinct non funziona se non c'e' un comparer perche' 2 istanze diverse della stessa classe, anche quando contenessero gli stessi identici valori, sarebbero di per se' differenti in quanto risiedono su diverse zone di memoria e il comparer di default si basa proprio su quello.

Se per caso CustomView fosse una collezione di DataRow, puoi usare il comparer Standard delle DataRow

aBinding.BindingSource = result.Distinct(DataRowComparer.Default).OrderBy(x => x.aField);


Altrimenti anche io propenderei per la soluzione con il group by

var Res = result.GroupBy(o => o.CampoA)
.Select(g => g.First())
.OrderBy(x => x.aField);

Kralizek
24-04-2012, 09:50
Il Distinct non funziona se non c'e' un comparer perche' 2 istanze diverse della stessa classe, anche quando contenessero gli stessi identici valori, sarebbero di per se' differenti in quanto risiedono su diverse zone di memoria e il comparer di default si basa proprio su quello.

Se per caso CustomView fosse una collezione di DataRow, puoi usare il comparer Standard delle DataRow

aBinding.BindingSource = result.Distinct(DataRowComparer.Default).OrderBy(x => x.aField);


Altrimenti anche io propenderei per la soluzione con il group by

var Res = result.GroupBy(o => o.CampoA)
.Select(g => g.First())
.OrderBy(x => x.aField);

un tipo anonimo non ha un comparer definito a compile time che compara i valori?

Per provare ho eseguito questa query con LinqPad su un database che avevo a portata di mano:


Institutes.Select(c => new
{
c.FKSiteID, c.IsPublished, c.HasLogo, c.FKUpdatedByID
}).Take(1000).Distinct().Dump();


ed l'sql risultante é correttamente


SELECT DISTINCT [t1].[FKSiteID], [t1].[IsPublished], [t1].[HasLogo], [t1].[FKUpdatedByID]
FROM (
SELECT TOP (1000) [t0].[FKSiteID], [t0].[IsPublished], [t0].[HasLogo], [t0].[FKUpdatedByID]
FROM [institute].[Institutes] AS [t0]
) AS [t1]


A questo punto basta creare il tipo anonimo con tutti i campi della tabella, e dopo il distinct, ricreare le entities con un parameter-less constructor.

RaouL_BennetH
24-04-2012, 11:56
Eccomi :)

utilizzando il metodo GroupBy effettivamente i duplicati vengono rimossi, soltanto che, in particolari circostanze, ad esempio un numero fattura uguale per cliente o fornitore presenti nella vista (es. fatt cliente 156, fatt fornitore 156) mi da un solo record se il raggruppamento viene fatto sul numdoc (e non ho altri campi validi per raggruppare).

Credo che sia sbagliata proprio la progettazione della vista a monte ma su quella posso fare ben poco.

Per lo scopo ho dovuto risolvere in un'altra maniera:

1) I dati mi servivano per generare un tracciato record per l'agenzia delle entrate
2) Generato il tracciato record con tutti i duplicati, sono andato ad agire sul tracciato per rimuoverli mediante:


File.WriteAllLine(@"trNoDup", lines.Distinct().ToArray());


Va a buon fine, nel senso che il controllo effettuato mediante entratel
non ne scarta più nessuno.

=KaTaKliSm4=
24-04-2012, 12:08
Eccomi :)

utilizzando il metodo GroupBy effettivamente i duplicati vengono rimossi, soltanto che, in particolari circostanze, ad esempio un numero fattura uguale per cliente o fornitore presenti nella vista (es. fatt cliente 156, fatt fornitore 156) mi da un solo record se il raggruppamento viene fatto sul numdoc (e non ho altri campi validi per raggruppare).

Credo che sia sbagliata proprio la progettazione della vista a monte ma su quella posso fare ben poco.

Per lo scopo ho dovuto risolvere in un'altra maniera:

1) I dati mi servivano per generare un tracciato record per l'agenzia delle entrate
2) Generato il tracciato record con tutti i duplicati, sono andato ad agire sul tracciato per rimuoverli mediante:


File.WriteAllLine(@"trNoDup", lines.Distinct().ToArray());


Va a buon fine, nel senso che il controllo effettuato mediante entratel
non ne scarta più nessuno.

A questo punto potevi utilizzare un comparer in memory....:)

L'importante è aver risolto, il principio del KISS è stato seguito alla perfezione :D

;)

gugoXX
25-04-2012, 01:06
un tipo anonimo non ha un comparer definito a compile time che compara i valori?

Per provare ho eseguito questa query con LinqPad su un database che avevo a portata di mano:


Institutes.Select(c => new
{
c.FKSiteID, c.IsPublished, c.HasLogo, c.FKUpdatedByID
}).Take(1000).Distinct().Dump();


ed l'sql risultante é correttamente


SELECT DISTINCT [t1].[FKSiteID], [t1].[IsPublished], [t1].[HasLogo], [t1].[FKUpdatedByID]
FROM (
SELECT TOP (1000) [t0].[FKSiteID], [t0].[IsPublished], [t0].[HasLogo], [t0].[FKUpdatedByID]
FROM [institute].[Institutes] AS [t0]
) AS [t1]


A questo punto basta creare il tipo anonimo con tutti i campi della tabella, e dopo il distinct, ricreare le entities con un parameter-less constructor.

Vero, ma non lo farei.
Se dovesse cambiare la struttura della tabella/vista sottostante, alla meglio non compili, alla peggio potresti perdere qualche riga senza accorgertene.

Resta comunque una buona soluzione.