IQueryable <t>返回单个记录

时间:2018-06-29 10:19:35

标签: linq entity-framework-core iqueryable

我正在使用EF Core,并尝试创建一个接受任何类型和单个值的查询提供程序。然后,我想运行一个查询以返回已将选定属性设置为传入值的传入项的FirstOrDefault。与此类似:

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

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

    }
}

我有几种类型,需要执行这种类型的查询。我试图避免必须为每种类型创建此类查询的重复项。 有任何想法吗?

2 个答案:

答案 0 :(得分:0)

您尝试做的事情可能是错误的……正如Panagiotis所写,您可以将lambda表达式传递给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,它是一个lambda表达式,因此已由编译器验证。您可能会:

Expression<Func<Product, string>> selector;

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

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

或通常将选择器保存在某个变量中/以任何方式选择正确的选择器。

答案 1 :(得分:0)

@PanagiotisKanavos评论了一个简单的解决方案。在这里稍作修改:

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

希望这对某人有帮助