我正在使用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;
}
}
}
我有几种类型,需要执行这种类型的查询。我试图避免必须为每种类型创建此类查询的重复项。 有任何想法吗?
答案 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 };
希望这对某人有帮助