使用匿名类型和用户定义类型的 LINQ select 查询

16

在C#中,匿名类具有只读属性。这通常用于在LINQ选择查询中声明以从数据库获取特定值。

在我的代码中,我有以下查询。让我感到困惑的是使用new语句选择新对象的匿名类。我有一个StudentClerkshipsLogModel模型类。当我使用模型名称时,查询结果允许编辑。

var query = (from entity in _tblStudentClerkshipsLog.GetQueryable()
             where entity.StudentID == intStudentID                             
             select new StudentClerkshipsLogModel
             {
                 StudentClerkshipID = entity.StudentClerkshipID,
                 StudentID = entity.StudentID,
                 ClerkshipID = entity.ClerkshipID,
             }).ToList();

当我在select语句中的new后面没有提及类型时,我无法退出,编译器会报错。匿名对象是只读的。

var query = (from entity in _tblStudentClerkshipsLog.GetQueryable()
             where entity.StudentID == intStudentID                             
             select new 
             {
                 StudentClerkshipID = entity.StudentClerkshipID,
                 StudentID = entity.StudentID,
                 ClerkshipID = entity.ClerkshipID,
             }).ToList()

我的问题是LINQ如何以不同的方式绑定这两个查询。这两个查询都有动态绑定,还是第一个查询是静态的。

谢谢


3
“我无法退出”是什么意思?编译器抱怨哪个代码?(我怀疑不是你展示的代码)你是否试图稍后修改对象?如果是,请展示代码。 - Jon Skeet
1
@JonSkeet:看起来像是打错了。"我无法编辑"。 - Dennis
6
它们只是不同而已。一个投影到匿名类型,另一个投影到命名类型。如果您稍后需要修改对象,则绝不能使用匿名类型,因为匿名类型的属性是只读的。这与LINQ直接无关... - Jon Skeet
@MuhammadNasir:为“更好”定义一个标准。 - Dennis
错误与发布的代码无关。错误来自于代码中其他地方使用查询时生成的。看起来您正在尝试编写查询对象。StudentClerkShipsLogModel是一个类,对象使用的内存量已经明确定义。因此可以进行编辑。在第二种情况下,您使用了一个匿名查询,其中对象的大小是动态定义的,并且可能会变化,因此它们无法被编辑。 - jdweng
显示剩余2条评论
3个回答

17

你遇到的错误实际上与LINQ无关。即使完全不使用LINQ,你也可以看到相同的情况:

var anonymous = new { Name = "Fred" };
anonymous.Name = "Joe"; // Error, as properties of anonymous types are read-only

因此,如果您想修改由LINQ查询获取的对象,则不应使用匿名类型。但是,两个LINQ查询都是静态绑定的 - 匿名类型在编译时仍然完全知道,并且编译器对它们应用正常的类型限制。例如:

var anonymous = new { Name = "Fred" };
Console.WriteLine(anonymous.Foo); // Error: no property Foo
int bar = anonymous.Name; // Error: no conversion from string to int

我知道这个,但我想知道LINQ如何处理它们,因为工作查询完全不同。我在问题中提到了匿名对象是只读的。 - Muhammad Nasir

11

如果我理解正确,你想知道LINQ提供程序如何设置匿名对象的属性,因为它们是“真正”的只读属性(没有任何私有设置,只有get)?

当你调用IQueryable<T>Select扩展方法时,它接受一个Expression<Func<T,TResult>类型的表达式。 如果你写一些Select的存根代码,你可以使用调试器查看生成的表达式:

public static class MyExtensions
{
    public static void MySelect<T, TResult>(this IQueryable<T> query, Expression<Func<T, TResult>> projection)
    {
        System.Diagnostics.Debug.WriteLine(projection);
    }
}

区别在于编译器如何为命名类型和匿名类型生成lambda表达式。当您为命名类型调用 Select 时,表达式将如下所示:

{_ => new Person() {Id = _.Id, Name = _.Name}}

即,首先会构造一个新的Person对象,然后初始化成员( MemberInit 表达式)。

但是当你为匿名类型调用Select时,表达式会被构建为构造函数调用(New表达式):

{_ => new <>f__AnonymousType0`2(a = _.Id, b = _.Name)}

LINQ提供程序将这些Lambda编译为委托,当实现查询结果并最终调用匿名类型的构造函数时。

2
我发现以下是使用LINQ结果的匿名类型的区别。
  1. 结果不可编辑,例如,如果我们将值分配给GridView,它将是只读的。

  2. 匿名对象的范围存在问题。我们无法将类型传递给另一个方法。定义一个 var 类型的参数; var 必须始终跟随初始化表达式。

如果您仅需要在当前上下文中以只读方式获取结果,请使用匿名查询。如果您需要在其他函数中获取结果,则必须定义对象的类型。在 new 后创建的对象类型将使用您从结果中获取的属性进行定义,并在大括号 {} 中定义它们。不必初始化模型类的所有值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接