2
votes

Comment générer une requête LINQ qui interroge toutes les propriétés non nulles à partir d'un modèle de page de recherche

J'ai deux modèles: TestRecord et Part où un TestRecord contient une référence à une Part : XXX

J'ai une page de recherche qui affiche une entrée pour chaque propriété d'un enregistrement de test, y compris ses propriétés associées telles que:

[HttpPost]
public List<TestRecord> Search(TestRecord testRecord){

    List<TestRecord> records = _db.TestRecords
                     .Where(tr => tr.TestType == testRecord.TestType)
                     .Where(tr => tr.Part.Name == testRecord.Part.Name).ToList();

    return records;
}

Quand je postez ceci sur mon contrôleur pour exécuter la requête, quelle est la meilleure façon de gérer cette requête? J'ai plus de 20 propriétés qui peuvent ou non être remplies sur la page qui contribue à filtrer une requête pour renvoyer un List .

Si je viens d'en avoir quelques propriétés à interroger ET je savais que leur remplissage était garanti Je pourrais faire quelque chose comme:

@model TestRecord
<!-- From TestRecord -->
<input asp-for="TestType" type="text" />
<!-- From TestRecord.Part assoc. prop -->
<input asp-for="Part.Name" type="text" />
....
And so on...

Comment générer une requête LINQ, comme mentionné ci-dessus, qui interroge pour tous propriétés du modèle qui ne sont pas nulles / vides? Ma seule option est-elle de coder en dur toutes les propriétés dans ma requête?


0 commentaires

4 Réponses :


2
votes

Ne pourriez-vous pas faire quelque chose comme ça?

[HttpPost]
public List<TestRecord> Search(TestRecord testRecord){

    List<TestRecord> records = _db.TestRecords
                 .Where(tr => String.IsNullOrEmpty(testRecord.TestType) ? true : tr.TestType == testRecord.TestType)
                 .Where(tr => String.IsNullOrEmpty(testRecord.Part.Name) ? true : tr.Part.Name == testRecord.Part.Name)
                 //etc...
                 .ToList();

    return records;
}

Filtrer uniquement si l'entrée est là pour chaque champ dans une grande requête?


1 commentaires

Je crois que ce sera la réponse la plus concise que je recherchais. Idéalement, ce serait génial de pouvoir simplement filtrer tous les attributs nuls avant de générer la requête, mais je suppose que c'est juste un vœu pieux haha. Je reviendrai un peu plus tard et s'il n'y a rien à ajouter ici, je marquerai ceci comme réponse



0
votes

La requête n'a pas besoin d'être une seule chaîne, vous pouvez la diviser et en insérer if:

var query = _db.TestRecords.AsQueryable();
if (string.IsNullOrEmpty(testRecord.TestType))
{
    query = query.Where(x => x.TestType == testRecord.TestType);
}
if (string.IsNullOrEmpty(testRecord.Part.Name))
{
    query = query.Where(x => x.Part.Name == testRecord.Part.Name);
}

// or, you can use an intermediate variable before returning to debug
return query.ToList();


0 commentaires

0
votes

J'utilise généralement une méthode d'extension comme celle-ci:

return s.Query()
    .Where(onlyStatus, p => p.Status == onlyStatus)
    .OrderByDescending(p => p.CreatedDate)
    .ToList();

Ensuite, vous pouvez composer votre requête LINQ comme ceci:

public static IQueryable<T> Where<T>(this IQueryable<T> that, object notNull, Expression<Func<T, bool>> predicate)
{
    if (!string.IsNullOrWhiteSpace(notNull?.ToString()))
    {
        return that.Where(predicate);
    }

    return that;
}


0 commentaires

0
votes

Si vous n'avez qu'une seule classe qui a cette exigence avec seulement un nombre limité de propriétés, disons moins de 20, je ne prendrais pas la peine de créer une solution générique pour cela. Codez un Where qui vérifie toutes les propriétés. Cela a l'avantage que si à l'avenir quelqu'un change ou supprime une propriété, votre compilateur se plaindra.

Une bonne solution serait de donner à votre classe une fonction d'extension:

static bool HasNullPrperties<TSource>(this TSource source)
    where TSource : class
{
     // Take the type of the source, and get all properties of this type
     var result = source.GetType().GetProperties()

         // keep only the readable properties (so you can do GetValue)
         // and those properties that have a nullable type
         .Where(property => property.CanRead 
             && Nullable.GetUnderlyingType(property.Type) != null)

         // for every of this properties, ask the source object for the property value:
        .Select(property => property.GetValue(source))

        // and keep only the properties that have a null value
        .Where(value => value == null);

        // return true if source has any property with a null value
        // = if there is any value left in my sequence
        .Any();
    return result;  
}

Utilisation quelque part dans une instruction LINQ

 public static IEnumerable<TSource> WhereHasNullProperties<TSource>(
      this IEnumerable<TSource> source)
      where TSource : IHasNullProperties
{
    return source.Where(item => item.HasNullProperties();
}

Si vous voulez une solution de preuve complète qui fonctionne sur plusieurs classes, envisagez de concevoir une interface:

interface IHasNullProperties
{
    bool HasNullProperties {get;}
}


2 commentaires

Salut Harald, merci pour la réponse détaillée, donc je voulais que cela soit étiqueté comme une question .NET Core, donc l'utilisation de statique serait probablement désagréable. Quels changements seraient nécessaires ici pour convertir cela en un service que je peux injecter dans mon contrôleur où j'exécute ma requête?


Les fonctions statiques que j'ai créées sont des méthodes d'extension (google: méthodes d'extension démystifiées). Chaque fonction LINQ standard est une méthode d'extension (regardez les définitions pour voir cela). Je me demande comment vous pouvez utiliser LINQ si vous ne pouvez pas utiliser de fonctions statiques.