4 Stimmen

Anzahl der eindeutigen mit linq to entities und benutzerdefiniertem IEqualityComparer

Mein benutzerdefinierter Vergleicher scheint nicht zu funktionieren. Ich möchte eine Zählung der unterschiedlichen Objekte, aber ich erhalte jedes Mal eine Zählung von 1. Auch wenn ein Blick auf die Datenbank selbst deutlich zeigt, gibt es mehr 1 Instanzen der Abfrage mit unterschiedlichen "TimeOfAction" Werte.

 class TimeComparer : IEqualityComparer<Action>
{
    public bool Equals(Action a, Action b)
    {
        if (a.TimeOfAction == b.TimeOfAction)
            return true;
        else
            return false;
    }

    public int GetHashCode(Action obj)
    {
        return obj.ToString().ToLower().GetHashCode();
    }
}

Ich denke, es könnte die GetHashCode-Methode sein, da ich nicht allzu vertraut mit der Funktionsweise bin. Hier ist die Linq-Abfrage. Ich konvertierte in AsEnumerable seit Linq to Entities unterstützt nicht die unterschiedliche Methode.

DBEntities db = new DBEntities();

        IEnumerable<Action> query = 
                    from action in db.Action.AsEnumerable()
                    where action.TimeOfAction > new DateTime(2010, 11, 1, 0, 0, 0)
                    where action.TimeOfAction < new DateTime(2011, 2, 7, 0, 0, 0)
                    where action.EntityName == "seant"
                    select action;

var count = query.   
            Distinct(new TimeComparer()).Count();

6voto

Jon Skeet Punkte 1325502

Ihre Equals- und GetHashCode-Methoden nehmen vollständig verschiedene Ansätze. Insbesondere können gleiche Objekte durchaus unterschiedliche Hash-Codes haben, wenn man annimmt Action.ToString andere Felder als TimeOfAction verwendet. Sie müssen aufeinander abgestimmt sein, sonst haben Sie keine Chance, vernünftige Ergebnisse zu erzielen. Es ist in Ordnung, wenn ungleiche Objekte denselben Hashcode haben (obwohl das die Leistung beeinträchtigt), aber gleiche Objekte muss ergeben denselben Hash-Code.

Beachten Sie, dass die Verwendung eines benutzerdefinierten Komparators dazu führt, dass der Distinct-Teil prozessintern und nicht in der Datenbank durchgeführt wird. Das mag kein Problem sein, Sie müssen es nur verstehen. EDIT: Ich hatte nicht bemerkt, dass es eine Überladung von Queryable.Distinct die tut einnehmen IEqualityComparer<T> . Meine Vermutung ist, dass das ist, so dass Sie benutzerdefinierte String-Vergleicher und ein paar andere bekannte Vergleicher bereitstellen können ... nicht nur willkürlichen Code. Wenn es funktioniert, wird es sowieso lokal gemacht werden. Ich wäre nicht überrascht, wenn es einfach explodieren würde.

EDIT: Wie Marc sagt, können Sie Select(x => x.TimeOfAction).Distinct().Count() um dies in der Datenbank zu tun. Sie müssen den Aufruf von AsEnumerable aber auch. Meine Vermutung ist, dass das so ist, weil etwas anderes nicht funktioniert hat. Sie könnten dies versuchen:

DBEntities db = new DBEntities();
IQueryable<DateTime> query = 
            from action in db.Action
            where action.TimeOfAction > new DateTime(2010, 11, 1, 0, 0, 0)
            where action.TimeOfAction < new DateTime(2011, 2, 7, 0, 0, 0)
            where action.EntityName == "seant"
            select action.TimeOfAction;
var count = query.Distinct().Count();

Natürlich, wenn Sie brauchen query für etwas anderes müssten Sie auch die Originalversion behalten:

DBEntities db = new DBEntities();
IQueryable<Action> query = 
            from action in db.Action
            where action.TimeOfAction > new DateTime(2010, 11, 1, 0, 0, 0)
            where action.TimeOfAction < new DateTime(2011, 2, 7, 0, 0, 0)
            where action.EntityName == "seant"
            select action;

var count = query.Select(x => x.TimeOfAction).Distinct().Count();
// Use query here as well to get at full action details

Beachten Sie, dass die erneute Verwendung von query eine zweite Datenbankabfrage erzeugt. Sie müssen sich ansehen, was in Bezug auf Transaktionen vor sich geht, wenn die Zählung mit der zweiten Abfrage übereinstimmen soll... oder alle Details aus der Datenbank abrufen (mit einer ToList aufrufen) und dann den Teil "Unterscheiden" prozessbegleitend durchführen.


Zurück zu benutzerdefinierten Gleichheitsvergleichen...

Angenommen, TimeOfAction ist eine DateTime oder einen anderen Typ, der einen vernünftigen Hash-Code hat, können Sie Ihre Klasse ändern:

class TimeComparer : IEqualityComparer<Action>
{
    public bool Equals(Action a, Action b)
    {
        return a.TimeOfAction == b.TimeOfAction;
    }

    public int GetHashCode(Action obj)
    {
        return obj.TimeOfAction.GetHashCode();
    }
}

Beachten Sie, dass ich auch Ihre Equals Methode - immer dann, wenn Sie sich in einer Situation befinden:

if (condition)
{
    return true;
}
else
{
    return false;
}

können Sie es vereinfachen:

return condition;

4voto

Marc Gravell Punkte 970173

Erstens ist zu beachten, dass ist nicht auf dem Datenbankserver ausgeführt wird, so dass dies nicht wirklich mit EF zusammenhängt.

I suspect es ist zum Teil weil Ihr GetHashCode ist nicht einverstanden mit Equals Sie sollten idealerweise etwas haben wie:

public int GetHashCode(Action obj)
{
    return obj.TimeOfAction.GetHashCode();
}

denn das ist es, was Ihr Equals kümmert sich um.

Beachten Sie jedoch auch, dass die gesamte Abfrage umgeschrieben werden kann (und wahrscheinlich auf dem Datenbankserver funktioniert, wenn Sie IQueryable<Action> query (und entfernen Sie die AsEnumerable() ), sondern als IEnumerable<Action> query ) mit:

var count = query.Select(x => x.TimeOfAction).Distinct().Count();

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X