IQueryable <T>для возврата одной записи - PullRequest
0 голосов
/ 29 июня 2018

Я использую EF Core и пытаюсь создать поставщика запросов, который принимает любой тип и одно значение. Затем я хочу выполнить запрос, чтобы вернуть FirstOrDefault переданных элементов, для которых выбранное свойство установлено в переданное значение. что-то похожее на это:

public class TagPicker<T>
{
    public IQueryable<T> Pick(IQueryable<T> source, string column, string filter)
    {
        T result = source.FirstOrDefault(r => r.column == value);

        if (result is null)
        {
            return new T { column = filter };
        }
        else {
            return result;
        }
    }
}

У меня есть несколько типов, которым понадобится выполнить этот тип запроса. Я стараюсь не создавать дубликаты запросов этого типа для каждого типа.

Есть идеи?

Ответы [ 2 ]

0 голосов
/ 30 июня 2018

@ PanagiotisKanavos прокомментировал простое решение для этого. Здесь это немного изменено:

Object.Foo = context.Foos.FirstOrDefault(x => x.Property == form.Value) ?? new Foo { Property = form.Value };

Надеюсь, это кому-нибудь поможет

0 голосов
/ 29 июня 2018

То, что вы пытаетесь сделать, вероятно, неправильно ... Как писал Панайотис, вы можете передать лямбда-выражение методу Pick. Но я постараюсь, чтобы ваш код работал. Вероятно, у вас проблема XY . Правильным решением было бы понять, что такое проблема Y и что такое решение Y. Вместо этого я дам решение проблемы X.

Теперь то, что вы пытаетесь сделать (помещая имя столбца в string), называется Dynamic Linq. Мы (где «мы» - большая группа программистов на C #) стараемся не использовать его, потому что это не очень «безопасно», потому что хранение имен столбцов внутри string делает код трудным для рефакторинга ... Но все же есть ниша ситуаций, где это необходимо. В .NET есть различные библиотеки для работы с Dynamic Linq. В настоящее время разрабатывается System.Linq.Dynamic.Core . Имеет очень практичный NuGet .

Пример кода:

using System.Linq.Dynamic.Core;

public static class TagPicker
{
    public static T Pick<T>(this IQueryable<T> source, string column, string filter) where T : class, new()
    {
        // Dynamic Linq supports query in the form *columnname = @0*,
        // where @0 is the first parameter (and @1 the second and so on)
        T result = source.FirstOrDefault(column + " = @0", filter);

        if (result is null)
        {
            // We use reflection to find the column *column* and
            // set its value to *filter*. Note that we don't try
            // to do a cast, so *column* must be of type *string*
            result = new T();
            typeof(T).GetProperty(column).SetValue(result, filter);
        }

        return result;
    }
}

, а затем использовать его как:

using (var context = new MyDbContext())
{
    var result = context.Products.Pick("ProductName", "Foo");
}

Теперь ... Из любопытства, что, вероятно, предложил Панагиотис:

public static T Pick<T>(this IQueryable<T> source, Expression<Func<T, string>> column, string filter) where T : class, new()
{
    Expression<Func<T, bool>> columnFilter = Expression.Lambda<Func<T, bool>>(Expression.Equal(column.Body, Expression.Constant(filter)), column.Parameters);

    T result = source.FirstOrDefault(columnFilter);

    if (result is null)
    {
        result = new T();
        Expression<Action<T>> assign = Expression.Lambda<Action<T>>(Expression.Assign(column.Body, Expression.Constant(filter)), column.Parameters);

        // If you can't compile with the true because you are using an old .NET, remove it
        Action<T> assignCompiled = assign.Compile(true);
        assignCompiled(result);
    }

    return result;
}

Используйте это как:

var result = context.Products.Pick(x => x.ProductName, "Foo2");

Обратите внимание, что теперь x.ProductName больше не является string, это лямбда-выражение, поэтому оно проверяется компилятором. Вы могли бы иметь:

Expression<Func<Product, string>> selector;

if (somecondition)
{
    selector = x => x.ProductName;
}
else
{
    selector = x => x.UnitName;
}

var result2 = context.Products.Pick(selector, "Foo2");

или вообще сохраните селекторы в некоторой переменной / выберите правильный селектор любым способом.

...