Ein allgemeines Validierungsattribut zum Überprüfen der Eindeutigkeit in einem Linq-zu-SQL-Datenkontext

StackOverflow https://stackoverflow.com/questions/2031547

Frage

Ich programmiere asp.net jetzt schon seit ein paar Tagen.Hier ist eine Frage, die ich selbst noch nicht einmal ansatzweise verstehen kann.

Ich hoffe, aus dem Code geht hervor, was ich erreichen möchte, und das habe ich auch, aber es ist nicht schön.Außerdem würde ich es gerne auf jeder Tabelle, jedem Feld verwenden, d.h.Überprüfen Sie die Eindeutigkeit eines Werts anhand einer von mir angegebenen Tabelle und eines Felds und übergeben Sie alles an den Attributkonstruktor.

public class UniqueEmailAttribute : ValidationAttribute
{
    public UniqueEmailAttribute()
    {
    }

    public override Boolean IsValid(Object value)
    {
        //not pretty. todo: do away with this.
        var db = new CoinDataContext();
        int c = db.Emails.Count(e => e.Email1 == value.ToString());
        return (Boolean) (c == 0);
    }
}
War es hilfreich?

Lösung

Das ist gerade eingetroffen asp.net-Foren von Brad Wilson.So zufrieden damit.Keine Fehlerbehandlung!

using System;
using System.ComponentModel.DataAnnotations;
using System.Data.Linq;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

public class UniqueAttribute : ValidationAttribute {
    public UniqueAttribute(Type dataContextType, Type entityType, string propertyName) {
        DataContextType = dataContextType;
        EntityType = entityType;
        PropertyName = propertyName;
    }

    public Type DataContextType { get; private set; }

    public Type EntityType { get; private set; }

    public string PropertyName { get; private set; }

    public override bool IsValid(object value) {
        // Construct the data context
        ConstructorInfo constructor = DataContextType.GetConstructor(new Type[0]);
        DataContext dataContext = (DataContext)constructor.Invoke(new object[0]);

        // Get the table
        ITable table = dataContext.GetTable(EntityType);

        // Get the property
        PropertyInfo propertyInfo = EntityType.GetProperty(PropertyName);

        // Our ultimate goal is an expression of:
        //   "entity => entity.PropertyName == value"

        // Expression: "value"
        object convertedValue = Convert.ChangeType(value, propertyInfo.PropertyType);
        ConstantExpression rhs = Expression.Constant(convertedValue);

        // Expression: "entity"
        ParameterExpression parameter = Expression.Parameter(EntityType, "entity");

        // Expression: "entity.PropertyName"
        MemberExpression property = Expression.MakeMemberAccess(parameter, propertyInfo);

        // Expression: "entity.PropertyName == value"
        BinaryExpression equal = Expression.Equal(property, rhs);

        // Expression: "entity => entity.PropertyName == value"
        LambdaExpression lambda = Expression.Lambda(equal, parameter);

        // Instantiate the count method with the right TSource (our entity type)
        MethodInfo countMethod = QueryableCountMethod.MakeGenericMethod(EntityType);

        // Execute Count() and say "you're valid if you have none matching"
        int count = (int)countMethod.Invoke(null, new object[] { table, lambda });
        return count == 0;
    }

    // Gets Queryable.Count<TSource>(IQueryable<TSource>, Expression<Func<TSource, bool>>)
    private static MethodInfo QueryableCountMethod = typeof(Queryable).GetMethods().First(m => m.Name == "Count" && m.GetParameters().Length == 2);
}

Andere Tipps

Schauen wir uns zunächst das Umschreiben des Attributs an ...

public override bool IsValid(object value)
{
    var db = new CoinDataContext();

    //Return whether none of the email contains the specified value
    return db.Emails.Count(e => e.Email1 == value.ToString()) == 0;
}

Es war auch nicht nötig, etwas zu gießen (c == 0) als boolescher Wert, da das Ergebnis dieser Operation bereits ein boolescher Wert ist.Und der Typ bool ist ein alias für Boolean auf die gleiche Weise int ist ein Alias ​​für Int32.Beides ist akzeptabel.Ich persönlich bevorzuge die Kleinbuchstabenversion.

Wie Alex bereits in seinem vorgeschlagen hat Antwort, wäre dies keine sichere Methode, um festzustellen, ob die E-Mail-Adresse eindeutig ist, wenn sie in die Datenbank aufgenommen wird.Nur, dass es zum Zeitpunkt der Überprüfung eindeutig ist.

Abschließend und etwas abwegig ...Ich habe geschrieben einige Linq-Erweiterungen wie die folgende Klasse.Wenn ich es verwende, könnte ich die Rückgabe des Attributs in umschreiben db.Emails.None(e => e.Email1 == value.ToString());.Das macht es zu einem ein kleines bisschen besser lesbar.

AktualisierenEs gibt keine Möglichkeit, die Eindeutigkeit eines Werts in der Datenbank zu bestimmen, ohne in die Datenbank zu gehen und die Zeilen mit den geschriebenen Werten zu vergleichen.Sie müssen noch eine Instanz zur Datenbank erstellen.Was ich jedoch tun würde, ist anzuschauen Trennung dieser Anliegen in Bereiche wie eine Serviceschicht und eine Datenschicht (separate Projekte vom MVC-Website-Projekt).Ihre Datenschicht würde ausschließlich alles verarbeiten, was mit der Datenbank zu tun hat.Wenn Sie möchten, kann ich einige Beispiele schreiben, wie Sie den CoinDataContext vom Attribut selbst trennen würden?

Um ein weiteres Ihrer Bedenken auszuräumen, entfernen wir hier die Notwendigkeit der Abfrage innerhalb des Attributs, Sie benötigen jedoch weiterhin einen Aufruf an die Datenbank und die Angabe, welche Tabelle Sie verwenden möchten.

Da es sich jedoch um ein Attribut handelt, bin ich mir nicht 100 % sicher, ob Sie Linq-Lambda-Ausdrücke im Attribut verwenden können. Daher muss Ihr Attribut auf diese Weise verallgemeinert bleiben.

Datenschichtprojekt

Diese Ebene würde verschiedene Klassen enthalten, die sich auf verschiedene Tabellen beziehen.Die folgende Klasse ist der E-Mail-Tabelle gewidmet.

E-Mail-Mapper-Klasse

public static class EmailMapper
{
  public static void IsValid(Func<string, bool> query)
  {
    var db = new CoinDataContext();
    return db.Emails.Count(query) == 0;
  }
}

Service-Layer-Projekt

Diese Schicht ist für die allgemeine Validierung von Objekten verantwortlich, wird aber auch für den Zugriff auf andere Schichten wie externe APIs verwendet.

EmailService-Klasse

public static class EmailService
{
  public static IsValid(string address)
  {
    bool isValid = false;

    //...Check email is valid first with regex. Not done.
    isValid = RegexHelper.IsEmailAddressValid(address);

    //Go to the database and determine it's valid ONLY if the regex passes.
    return isValid ? EmailMapper.IsValid(x=> x.Email == address) : false;
  }
}

Attributklasse im Webprojekt

public override Boolean IsValid(Object value)
{
    return EmailService.IsValid(value.ToString());
}

Ich mag LINQ nicht, aber es scheint, dass Sie versuchen, die Einzigartigkeit clientseitig durchzusetzen.Das ist einfach nicht möglich.Eindeutigkeitsbeschränkungen muss in der Datenbank erzwungen werden.Was passiert Ihrer Meinung nach, wenn eine gleichzeitige Transaktion eine E-Mail-Adresse direkt nach der Überprüfung festschreibt?

Selbst wenn Sie dies nur tun, um die Meldung „Diese Adresse wird leider bereits verwendet“ anzuzeigen, ist dies der Fall Trotzdem Es besteht die Möglichkeit, dass eine andere Transaktion dieselbe Adresse einfügt.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top