Как создать запрос LINQ, который запрашивает все ненулевые свойства из модели страницы поиска - PullRequest
1 голос
/ 25 апреля 2019

У меня есть две модели: TestRecord и Part, где TestRecord содержит ссылку на Part:

TestRecord
----
public string TestRecordId
public int PartId <-- foreign key to parts table
public string Name
public string TestType
public virtual Part Part

Part
----
int PartId
string Name
string Description
public virtual ICollection<TestRecord> TestRecords

У меня есть страница поиска, на которой отображаются входные данные для каждого свойства тестовой записи, включая связанные с ним свойства, такие как:

@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...

Когда я отправляю это на свой контроллер, чтобы выполнить запрос, каков наилучший способ обработать этот запрос? У меня есть 20+ свойств, которые могут или не могут быть заполнены на странице, которая способствует фильтрации запроса, чтобы вернуть List<TestRecord>.

Если бы у меня просто было несколько свойств для запроса И я знал, что они были гарантированно заполнены, я мог бы сделать что-то вроде:

[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;
}

Как бы я сгенерировал запрос LINQ, как упоминалось выше, который запрашивает все свойства из модели, которые не являются нулевыми / пустыми? Является ли моя единственная возможность жестко закодировать все свойства в моем запросе?

Ответы [ 4 ]

2 голосов
/ 25 апреля 2019

Не могли бы вы просто сделать что-то подобное?

[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;
}

По существу, только фильтр, если есть вход для каждого поля в 1 большом запросе?

0 голосов
/ 25 апреля 2019

Если у вас есть только один класс, имеющий это требование с ограниченным числом свойств, скажем, менее 20, я бы не стал создавать универсальное решение для этого. Код Where, который проверяет все свойства. Это имеет то преимущество, что если в будущем кто-то изменит или удалит свойство, ваш компилятор будет жаловаться.

Хорошим решением было бы дать вашему классу функцию расширения:

public static bool HasNullProperties(this MyClass x)
{
    return x.Name == null
        && x.Location == null
        && x.OrderSize == null
        ...;
}

public static IEnumerable<MyClass> WhereHasNullProperties(this IEnumerable<MyClass> source)
{
    return source.Where(item => item.HasNullProperties();
}

Использование где-то в операторе LINQ

var result = dbContext.MyItems.WhereHasNullProperties()
    .GroupBy(...)
    .Select(...);

Если вы хотите получить полное пробное решение, которое работает на нескольких классах, подумайте о разработке интерфейса:

interface IHasNullProperties
{
    bool HasNullProperties {get;}
}

Ваша функция LINQ будет:

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

Наконец, относительно медленным методом было бы использовать отражение: для любого класса получить все его свойства get, которые можно обнулять, и посмотреть, имеет ли какое-либо из них нулевое значение:

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;  
}
0 голосов
/ 25 апреля 2019

Я обычно использую метод расширения, подобный этому:

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;
}

Затем вы можете составить свой запрос LINQ следующим образом:

return s.Query()
    .Where(onlyStatus, p => p.Status == onlyStatus)
    .OrderByDescending(p => p.CreatedDate)
    .ToList();
0 голосов
/ 25 апреля 2019

Запрос не должен быть одной цепочкой, вы можете разделить его и вставить несколько 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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...