[Route("{year:min(2000)}/{month:range(1,12)}/{key}")]
public IActionResult Post(int year, int month, string key)
{
var post = _db.Posts.FirstOrDefault(x => x.Key == key);
return View(post);
}
嗨, 我正在用C#在ASP.NET Core中做到这一点。
对我来说模糊的部分是:_db.Posts.FirstOrDefault(x => x.Key == key);
所以我猜是:
答案 0 :(得分:3)
- 参数x已通过(尽管我并没有完全通过它)。
不,这不会发生。传递的是表达式,定义了匿名函数。使用=>
运算符时,此类表达式通常称为 lambda表达式。 x
是表达式的一部分,它确定函数的调用方式。它是函数表达式使用的输入变量的占位符。
如果我为您提供如何实现FirstOrDefault()
方法的假装版本,它将帮助您理解:
public T FirstOrDefault<T>(this IEnumerable<T> items, Func<T, boolean> predicate)
{
foreach(T item in items)
{
if(predicate(item)) return item;
}
return default(T);
}
该代码中应理解的一些事情:
第一个参数前面的 this
将功能转换为扩展方法。无需使用两个参数调用该方法,而是跳过第一个参数...仅使用第二个参数调用它,就好像它是第一个参数的类型的成员一样。即_db.Posts.FirstOrDefault(foo)
代替FirstOrDefault(_db.Posts, foo)
。
表达式中的key
变量称为闭包。即使未将其作为参数传递,它也可以作为此方法内predicate()
函数的一部分使用。这就是predicate(item)
调用仅用true
作为输入就能确定false
或item
的原因。
此方法中的predicate()
函数调用已作为参数传递给该方法。这就是x => x.Key == key
参数的解释方式;它成为predicate()
函数使用的FirstOrDefault()
方法。您可以认为predicate()
是这样定义的:
bool predicate(T x)
{
return x.Key == key;
}
C#编译器会自动为您进行转换,甚至为T
推断正确的运行时类型,并自动处理key
闭包的范围。
答案 1 :(得分:1)
其他答案很接近,但并不完全正确。
我假设_db
是Entity Framework DbContext
,而_db.Posts
是DbSet<Post>
。
因此,您看到的.FirstOrDefault()
方法实际上是Extension method,而x => x.Key == key
部分是Expression tree。
幕后发生的事情是,对_db.Posts.FirstOrDefault(x => x.Key == key)
的调用被转换为类似SELECT TOP(1) Key, Content, ... FROM posts WHERE Key = @key
的SQL语句,其结果被映射到Post
实体中。
有很多语言功能可以使所有这些工作正常进行,所以让我们看一下!
扩展方法是静态方法,但可以像实例方法一样调用。
它们在静态类中定义,并具有“接收器”参数。对于FirstOrDefault
,扩展方法如下:
public static class Queryable {
public static T FirstOrDefault<T>(this IQueryable<T> source, Expression<Func<T, bool>> predicate = null) {
// do something with source and predicate and return something as a result
}
}
用法_db.Posts.FirstOrDefault(...)
实际上是语法糖,它将由C#编译器转换为称为la Queryable.FirstOrDefault(_db.Posts, ...)
的静态方法。
请注意,尽管使用了语法糖,但扩展方法仍然是静态方法,无法访问其接收者的内部状态。他们只能访问公共成员。
C#支持称为代理的伪一流函数。有几种实例化委托的方法。
它们可以用于capture
现有方法,也可以使用匿名函数进行初始化。
使用匿名函数初始化委托的最优雅的方法是使用lambda样式函数,例如x => x + 10
或(x, y) => x + y
。
在这些示例中看不到类型注释的原因是,在许多常见情况下,编译器可以推断参数的类型。
这里是另一个示例:
// This is a normal function
bool IsEven(int x) {
return x % 2 == 0;
}
// This is an anonymous function captured in a delegate of type `Func<T1, TResult>`
Func<int, bool> isEven = x => x % 2 == 0;
// You can also capture methods in delegates
Func<int, bool> isEven = IsEven;
// Methods can be called
int a = IsEven(5); // result is false
// Delegates can be called as well
int b = isEven(4); // result is true
// The power of delegates comes from being able to pass them around as arguments
List<int> Filter(IEnumerable<int> array, Func<int, bool> predicate) {
var result = new List<int>();
foreach (var n in array) {
if (predicate(n)) {
result.Add(n);
}
}
return result;
}
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
var evenNumbers = Filter(numbers, isEven); // result is a list of { 2, 4, 6 }
var numbersGt4 = Filter(numbers, x => x > 4); // result is a list of { 5, 6 }
C#编译器具有一项功能,使您可以使用外观正常的代码创建表达式树。
例如,Expression<Func<int, int>> add10Expr = (x => x + 10);
不会使用实际函数而是使用表达式树(它是对象图)来初始化add10Expr
。
手动初始化,看起来像这样:
Expression xParameter = Expression.Parameter(typeof(int), "x");
Expression<Func<int, int>> add10Expr =
Expression.Lambda<Func<int, int>>(
Expression.Add(
xParameter,
Expression.Constant(10)
),
xParameter
);
(这很麻烦)
表达式树的强大之处在于能够在运行时创建,检查和转换它们。
实体框架的作用是:将这些C#表达式树转换为SQL代码。
将所有这些功能结合在一起,您就可以用C#编写谓词和其他代码,然后通过Entity Framework将其转换为SQL,其结果将作为常规C#对象“物化”。
您可以在C#的便利范围内将复杂的查询全部写入数据库。 最好的是,您的代码是静态键入的。
答案 2 :(得分:0)
x是调用函数的对象的范围变量。您将在foreach (var x in _db.Posts)
中获得的对象相同,然后遍历该集合以寻找x.Key == key
,并返回满足该条件的第一个对象。因此该函数将返回db.Posts
中第一个对象,其中Key == key
编辑:更正的字词
答案 3 :(得分:0)
您的带有FirstOrDefault
的lambda表达式等效于以下扩展方法
public static Post FirstOrDefault(this YourDBType _db, string Key)
{
foreach(Post x in _db.Posts)
{
if(x.Key == Key)
{
return x
}
}
return null
}
X不是参数,它只是引用您正在处理的集合中单个项目的一种简便方法,就像在foreach语句中那样。问题的最后一步是“要么返回与我们比较的键相同的第一个Post,要么返回Post对象的默认值(对象为null)”