Hello World
Spiga

当类型为dynamic的视图模型遭遇匿名对象

2010-05-11 10:16 by 老赵, 7917 visits

当年在ASP.NET MVC 1.0时代我提到,在开发时最好将视图的Model定制为强类型的,这样可以充分利用静态检查功能进行排错。不过有人指出,这么做虽然易于静态检查,但是定义强类型的Model类型实在是太麻烦了,因此也出现了基于SmartBag等折衷方案。强类型是一种极端方案,而在C# 4.0中我们又可以使用另一个极端,那就是让Model成为dynamic类型,这样在视图中便可以完全自由地获取数据了。不过,在使用匿名对象的情况下视图会抛出奇怪的“无法找到成员”异常,我们必须解决这个问题。

dynamic类型的视图模型

我们现在先来创建一个Model类型为dynamic的视图,例如Views\Home\Index.aspx,我们要在一开始的Page标记中设置它的基类类型:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>

我们将这里的范型参数设为dynamic。这一做法可能会让某些朋友感到新奇,不过这其实十分正常。事实上,dynamic关键字在C#的概念中(不论实现),其实就是和int、string或是Controller一样,都是一种“类型”,只不过对于这样一个类型会有些动态分发等特殊处理。这样定义视图类型之后,便可以在Controller中自由提供视图模型了,例如:

public ActionResult Index()
{
    dynamic model = new ExpandoObject();
    model.Hello = "World";

    return View(model);
}

ExpandoObject是.NET 4.0在BCL中提供的类,它的作用便是利用.NET中定义的动态分发功能,定义了一个可以任意扩展的类型。例如在上面的代码中,我们可以直接赋予它Hello属性。然后在视图模板中读出:

<%= Model.Hello %>

这样看起来是不是很方便?这样虽然没有了任何的静态教研,倒也带来了十足的自由性——就像是Ruby on Rails等动态语言的框架一样。

奇怪的“无法找到成员”异常

不过,这种dynamic有时候会有非常奇怪的表现,例如我们把上面Index的代码改成使用匿名类型对象之后:

public ActionResult Index()
{
    return View(new { Hello = "World" });
}

再次执行就会抛出异常:

简单说来,便是指Model对象没有找到合适的Hello成员,因此绑定失败。但是,我们明明有这个属性,不是吗?更奇怪的是,如果是在一个Console应用程序中这么写则是没有任何问题的:

dynamic model = new { Hello = "World" };
Console.WriteLine(model.Hello);

问题似乎就是出在ASP.NET的页面上。说到原因,我也不知道,一开始我以为是由于只读属性造成的,但是在尝试了显式定义类型之后发现也不是这个原因。我相信仔细分析之后一定可以得出结果,不过现在最重要的是想个办法解决它。毕竟这个情况很容易遇到,ExpandoObject只能解决极小部分的问题。因为在Controller中的常见需求之一,便是使用如LINQ表达式等方式生成一些供视图使用的匿名对象。

生成动态类型

这个问题以前也有人遇到过,他的做法是为视图模型定义一个封装类。可惜这么做其实并没有解决问题,事实上它只解决了Model本身使用匿名对象的问题,但是如果是Model的某个字段返回一个匿名对象呢?再访问这个匿名对象还是有相同问题出现。幸好我们知道一个显示定义的类型是可以正常使用的,于是一个比较容易想到的方法便是在运行时生成动态类型。例如:

public static class DynamicFactory
{
    private static ConcurrentDictionary<Type, Type> s_dynamicTypes = new ConcurrentDictionary<Type, Type>();

    private static Func<Type, Type> s_dynamicTypeCreator = new Func<Type, Type>(CreateDynamicType);

    public static object ToDynamic(this object entity)
    {
        var entityType = entity.GetType();
        var dynamicType = s_dynamicTypes.GetOrAdd(entityType, s_dynamicTypeCreator);

        var dynamicObject = Activator.CreateInstance(dynamicType);
        foreach (var entityProperty in entityType.GetProperties())
        {
            var value = entityProperty.GetValue(entity, null);
            dynamicType.GetField(entityProperty.Name).SetValue(dynamicObject, value);
        }

        return dynamicObject;
    }

    private static Type CreateDynamicType(Type entityType)
    {
        var asmName = new AssemblyName("DynamicAssembly_" + Guid.NewGuid());
        var asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
        var moduleBuilder = asmBuilder.DefineDynamicModule("DynamicModule_" + Guid.NewGuid());

        var typeBuilder = moduleBuilder.DefineType(
            entityType.GetType() + "$DynamicType",
            TypeAttributes.Public);

        typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);

        foreach (var entityProperty in entityType.GetProperties())
        {
            typeBuilder.DefineField(entityProperty.Name, entityProperty.PropertyType, FieldAttributes.Public);
        }

        return typeBuilder.CreateType();
    }
}

我在这里定义了一个ToDynamic扩展方法,用于从任意对象扩展出一个……动态类型的对象。这个动态类型会根据输入对象中的属性信息,生成对应的公有字段,然后使用反射进行赋值。有了这个方法以后,遇到匿名类型也就只需稍多一步就够了:

return View(new { Hello = "World" }.ToDynamic());

即便像我之前所说的那样,使用LINQ语句为视图准备一些可用的匿名对象,也可以这样:

var categories = ...; // get categories;

dynamic model = new ExpandoObject();
model.PrivateCategories =
    from c in categories
    where c.AccessLevel == AccessLevel.Private
    orderby c.IsDefault descending, c.Name
    select new
    {
        CategoryID = c.NoteCategoryID,
        CategoryName = c.Name,
        IsDefault = c.IsDefault,
        Count = c.NoteCount
    }.ToDynamic();

model.PublicCategories =
    from c in categories
    where c.AccessLevel == AccessLevel.Public
    orderby c.IsDefault descending, c.Name
    select new
    {
        CategoryID = c.NoteCategoryID,
        CategoryName = c.Name,
        IsDefault = c.IsDefault,
        Count = c.NoteCount
    }.ToDynamic();

return View(model);

问题就这样解决了。

改进

很显然,上面的实现只是个雏形,其中最大的问题应该就是“性能”了。现在的代码中反复使用了反射,对此我们可以使用如Fast Reflection Library那样的方式来改善反射读写字段的执行效率。当然,可能最好的办法是动态生成出这样的代码了:

public class DynamicType
{
    public DynamicType(object entity)
    {
        var strongTyped = (EntityType)entity;

        this.Abc = strongTyped.Abc;
        this.Ijk = strongTyped.Ijk;
        this.Xyz = strongTyped.Xyz;
        ...
    }
}

这其实也很简单,有兴趣的话您也可以试试看。一会儿我也会去实现一下类似的功能,顺便尝试一下.NET 4.0——据说.NET 4.0的类库对生成动态方法较之前的版本有了更好的支持。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

63 条回复

  1. 链接

    叶丰 2010-05-11 10:32:55

    先坐下,慢慢研读!

  2. 链接

    宁静 2010-05-11 11:26:17

    很不错,学习一下。

  3. 链接

    童刚刚 2010-05-11 12:37:13

    我来学习了

    ExpandoObject是.NET 4.0在BCL中提供的类库

    这而是不是应该写成ExpandoObject是.NET 4.0在BCL中提供的类吧,类库是程序集了

  4. JustForFun
    221.226.17.*
    链接

    JustForFun 2010-05-11 13:16:54

    老赵的技术心态非常开放!应该和云风大人是一个级别的。

  5. 小羊肖恩
    119.161.160.*
    链接

    小羊肖恩 2010-05-11 13:31:54

    有一点点怀疑是因为MVC是基于.NET 3.5但是dynamic是4.0的特性,所以不能用。

  6. 老赵
    admin
    链接

    老赵 2010-05-11 13:36:29

    @小羊肖恩

    执行环境明显是.NET 4.0,而且也只是遇到匿名对象时才不能用,其他都好的,所以我觉得应该不是这个问题。

  7. 链接

    童刚刚 2010-05-11 13:44:00

    我试了下,在vb.net下,居然没有你说的这个问题,呵呵呵,少了不少事。

  8. 链接

    老菜 2010-05-11 17:29:04

    这段代码写得不错,学习。

  9. JimLiu
    221.223.44.*
    链接

    JimLiu 2010-05-11 21:30:14

    没搞明白为什么会出现这样奇怪的问题,虽然这样能解决不过多少有点饮鸩止渴的感觉。希望下个版本的MVC能patch一下,或者老赵的MVC patch能搞定它。

  10. Snake
    120.33.92.*
    链接

    Snake 2010-05-11 22:23:33

    鼓掌鼓掌~!

  11. 链接

    oulvhai 2010-05-11 22:50:41

    我其实最关心的是这种方法和强类型的对比性能上差多少。

    ps: 目前我一直在用ViewData的方式,没有用强类型。

  12. 老赵
    admin
    链接

    老赵 2010-05-11 23:49:23

    @JimLiu: 没搞明白为什么会出现这样奇怪的问题,虽然这样能解决不过多少有点饮鸩止渴的感觉。希望下个版本的MVC能patch一下,或者老赵的MVC patch能搞定它。

    这个应该不是靠简单的patch能解决的问题。

  13. 老赵
    admin
    链接

    老赵 2010-05-11 23:50:16

    oulvhai: 我其实最关心的是这种方法和强类型的对比性能上差多少。

    性能应该是数量级上的差别,不过这里够用即可,影响性能的因素实在太多了。

  14. 链接

    Ivony 2010-05-12 00:32:54

    的确是灰常奇怪的问题

    不是ASP.NET的问题,ASP.NET会产生正确的代码,因为你可以在页面直接定义dynamic变量。 类型也没问题,首先从调试器中能看到类型是正确的,其次,用DataBinder.Eval反射获取就没问题。

    问题出在动态生成的代码里,一个动态产生的类型和方法。

  15. 看看
    222.92.42.*
    链接

    看看 2010-05-12 08:52:12

    还没上4.0,留个记号再看。。

  16. JimLiu
    221.223.81.*
    链接

    JimLiu 2010-05-12 09:23:32

    这的确很奇怪,因为Debug在这里捕捉异常的时候Model还是正确的dynamic类型并且属性也在,但就是莫名其妙地抛了这么个异常。

  17. 老赵
    admin
    链接

    老赵 2010-05-12 11:12:43

    果然不发到博客园就没什么人来呢。

  18. baihmpgy
    117.22.228.*
    链接

    baihmpgy 2010-05-12 18:31:16

    使用RSS照样可以看到你的文章,:)。

  19. zhangronghua
    218.30.165.*
    链接

    zhangronghua 2010-05-14 15:25:54

    @老赵

    这不是还有这们些google reader用户吗?

  20. 链接

    装配脑袋 2010-05-14 23:42:27

    测试一下我登陆之后的回复^_^

  21. swanky.wu
    219.142.130.*
    链接

    swanky.wu 2010-05-20 12:26:13

    我也遭遇到此问题,但我觉得,其发生的原因可能与MVC框架或asp.net页面没有关系:当用dynamic在某个程序集中定义动态类型后,然后使用匿名类型对象对其赋值,再然后在另一个程序集访问这个动态类型的匿名对象的属性就会发生这个异常;在同一个程序集中访问中没有发现问题。

    我想是不是与匿名对象的作用域有关,当匿名对象用dynamic关键字修饰的时候,就会导致编译成“作用域有问题”程序呢?还要好好研究一下才会知道结果。

  22. 链接

    Ivony 2010-05-21 02:28:27

    我来推进一把这个问题的解决吧。

    不需要是匿名类型,只要可见性不符合要求,就会出问题。即使在同一个程序集。

  23. Zhenway
    61.172.241.*
    链接

    Zhenway 2010-05-21 08:53:31

    麻烦,不如直接修改生成的程序集(用Cecil等工具),把生成的匿名类型的可见性修改为public,问题就搞定了

  24. 老赵
    admin
    链接

    老赵 2010-05-21 09:17:56

    @Zhenway

    是可以搞定,不过我也不知道这是不是更方便了,估计需要仔细地和构建工具集成一下才行,呵呵。

  25. Neil Chen
    210.184.115.*
    链接

    Neil Chen 2010-06-02 21:09:53

    有点小问题,

    var typeBuilder = moduleBuilder.DefineType(
        entityType.GetType() + "$DynamicType",
        TypeAttributes.Public);
    

    这个应该是:

    var typeBuilder = moduleBuilder.DefineType(
        entityType + "$DynamicType",
        TypeAttributes.Public);
    

    entityType.GetType() 都会返回同一个的一个 "RuntimeType",从而会导致多次对不同类型调用 .ToDynamic() 时失败。

    第二个问题是

    typeBuilder.DefineField(entityProperty.Name, entityProperty.PropertyType, FieldAttributes.Public); 
    

    field 不支持 DataBinder.Eval() 方式的绑定。我改成了 property:

    var getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
    
    foreach (var entityProperty in entityType.GetProperties())
    {
        var fld = typeBuilder.DefineField("_" + entityProperty.Name, entityProperty.PropertyType, FieldAttributes.Private);
        //typeBuilder.DefineField(entityProperty.Name, entityProperty.PropertyType, FieldAttributes.Public);
        // Define property
        var propertyBuilder = typeBuilder.DefineProperty(entityProperty.Name, PropertyAttributes.HasDefault, entityProperty.PropertyType, Type.EmptyTypes);
    
        ILGenerator ilGen;
    
        // Define property get method
        var getMethod = typeBuilder.DefineMethod("get_" + entityProperty.Name, getSetAttr, entityProperty.PropertyType, Type.EmptyTypes);
        ilGen = getMethod.GetILGenerator();
        ilGen.Emit(OpCodes.Ldarg_0);
        ilGen.Emit(OpCodes.Ldfld, fld);
        ilGen.Emit(OpCodes.Ret);
    
        // Define property set method
        var setMethod = typeBuilder.DefineMethod("set_" + entityProperty.Name, getSetAttr, null, new[] { entityProperty.PropertyType });
        ilGen = setMethod.GetILGenerator();
        ilGen.Emit(OpCodes.Ldarg_0);
        ilGen.Emit(OpCodes.Ldarg_1);
        ilGen.Emit(OpCodes.Stfld, fld);
        ilGen.Emit(OpCodes.Ret);
    
    
        propertyBuilder.SetGetMethod(getMethod);
        propertyBuilder.SetSetMethod(setMethod);
    }
    
  26. 老赵
    admin
    链接

    老赵 2010-06-02 21:33:31

    @Neil Chen: entityType.GetType() 都会返回同一个的一个 "RuntimeType",从而会导致多次对不同类型调用 .ToDynamic() 时失败。

    没听明白,我用下来一直没有问题啊。

  27. Neil Chen
    124.79.171.*
    链接

    Neil Chen 2010-06-02 23:48:44

    entityType 的类型以及是 Type 了,再执行一次 entityType.GetType() 就会返回 System.RuntimeType,所以这里:

    var typeBuilder = moduleBuilder.DefineType(
        entityType.GetType() + "$DynamicType",
        TypeAttributes.Public);
    

    这里 DefineType() 的参数就总是RuntimeType$DynamicType,而不管真正的 entityType 是什么。

    假如你调用:

    var foo = new { Foo = "Hello" }.ToDynamic();
    var bar = new { Bar = "World" }.ToDynamic();
    

    在第二次调用的时候这个语句会尝试去用同样的类型名称再调用一次 DefineType 方法,这时候就会报异常。

    你的这个例子没有报错:

    model.PrivateCategories =
        from c in categories
        where c.AccessLevel == AccessLevel.Private
        orderby c.IsDefault descending, c.Name
        select new
        {
                CategoryID = c.NoteCategoryID,
                CategoryName = c.Name,
                IsDefault = c.IsDefault,
                Count = c.NoteCount
        }.ToDynamic();
    
    model.PublicCategories =
        from c in categories
        where c.AccessLevel == AccessLevel.Public
        orderby c.IsDefault descending, c.Name
        select new
        {
                CategoryID = c.NoteCategoryID,
                CategoryName = c.Name,
                IsDefault = c.IsDefault,
                Count = c.NoteCount
        }.ToDynamic();
    

    也许是因为这里生成的两个匿名类型恰好有同样的属性集,所以实际就共用了同一个类型。实际只调用了一次创建动态类型的操作。

  28. Neil Chen
    124.79.171.*
    链接

    Neil Chen 2010-06-03 00:05:47

    不好意思是我搞错了。这个代码没有问题的。我之所以测出问题是因为我之前把

    var asmName = new AssemblyName("DynamicAssembly_" + Guid.NewGuid());
    

    这些语句移到了成员变量里面。这样每次都共用了同一个 assembly.

    你现在的代码每次都是一个独立的 assembly, 我觉得如果公用一个可能开销会小一些。里面每个类型名改成用互不相同的。

  29. Neil Chen
    124.79.171.*
    链接

    Neil Chen 2010-06-03 00:07:16

    这里为什么不能修改 comments,好像某些功能不如 cnblogs 方便 :)

  30. 老赵
    admin
    链接

    老赵 2010-06-03 00:45:53

    @Neil Chen

    其实一个项目里匿名类不会多的,就算有一两百个动态生成的程序集其实也没什么,呵呵。

    不能修改评论是因为你没有登录,建议登录,以后的功能也很可能要求登录的。我这个评论系统花了很大精力开发啊,包括代码着色等等都有实时预览的,其实你在发表评论之前就能看到一模一样的效果了。具体怎么用看一下评论的帮助吧。

  31. Neil Chen
    124.79.171.*
    链接

    Neil Chen 2010-06-03 01:07:45

    haha, 多谢你还帮我调整好回复的代码格式。下次我多看看帮助自己设置哦 :)

  32. 沐枫
    120.32.59.*
    链接

    沐枫 2010-06-03 15:19:27

    Console应用程序其实也不例外的会出问题。 问题的关键在于,匿名对象的跨Assembly访问造成的。

    一个很简单的在Console中重现这个问题的代码如下:

    a.dll

    public class class1 {
        public object fun1() {
            return new {age=10};
        }
    }
    

    c.exe

    ...
    public void main(...) {
        dynamic data = new class1().fun1();
        int age = data.age; //error!!!
    }
    
  33. 沐枫
    120.32.59.*
    链接

    沐枫 2010-06-03 15:50:36

    原因似乎是找到了,但要想解决,确实困难啊。

    问题出在dynamic的编译代码上。

    假设有 dynamic d = new otherassembly.class1().fun1();

    则: int d = d.age; 将生成如下代码:

    var p__Site2 = CallSite<Func<CallSite, object, object>>.Create(Binder.GetMember(CSharpBinderFlags.None, "age", typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
    var temp = p__Site2.Target.Invoke(p__Site2, a); //ERROR!!!
    
    var p__Site1 = CallSite<Func<CallSite, object, int>>.Create(Binder.Convert(CSharpBinderFlags.None, typeof(int), typeof(Program)));
    int age = p__Site1.Target.Invoke(p__Site1, temp);
    

    其中 p__Site2 赋值这一句是关键所在。似乎dynamic总是用当前 class 做为类型参数:typeof(Program)

    结果呢,此匿名对象却是在另一个 otherassembly 中定义的,于是找不到"age"属性。事实上也许连类型都找不着,所以报错。

    假如,手工录入以上的代码,并将 typeof(Program) 改成 typeof(otherassembly.class1) ,运行结果就正确了。

    --

    假如我上面的分析是正确的话,那么这个补丁难道要等到sp1?

  34. 沐枫
    120.32.59.*
    链接

    沐枫 2010-06-03 16:10:30

    看起来,我的分析是错误的。

    真正的原因也许很贴近了:如果一个类型是internal的,那么,它不应在其它assembly中被访问。

    很不幸,匿名类默认的定义恰好就是internal的。杯具啊。

    如果这才是真正的原因的话,那么,可能连sp1都没得等了。

  35. 沐枫
    120.32.59.*
    链接

    沐枫 2010-06-03 16:19:42

    晕,10天前就有人想到了。浪费了我一小时。

  36. 老赵
    admin
    链接

    老赵 2010-06-03 17:09:27

    @沐枫: 真正的原因也许很贴近了:如果一个类型是internal的,那么,它不应在其它assembly中被访问。很不幸,匿名类默认的定义恰好就是internal的。杯具啊。如果这才是真正的原因的话,那么,可能连sp1都没得等了。

    没错,出现这个问题是by design的结果,不是bug,不会改变的。

  37. 沐枫
    120.32.59.*
    链接

    沐枫 2010-06-04 10:02:53

    ToDynamic 还有一种实现,可以考虑:

    public static object(this object entity) {
        return new EntityDynamicObject(entity);
    }
    

    EntityDynamicObject 继随自DynamicObject,只需实现 copy构造,TryGetMember即可:

    public class EntityDynamicObject: DynamicObject {
        private object _entity;//由copy构造函数赋值
        public override bool TryGetMember(GetMemberBinder binder, out object result) {
            Type type = _o.GetType();
            var pi = type.GetProperty(binder.Name);
            result = pi==null ? null : pi.GetValue(_entity, null);
            return pi != null;
        }
    ...
    

    当然,TryGetMember的实现完全可以用更高效的方法。

    TryGetMember是根据需要被调用的;可优化的地方还有很多,可用表达式树,可加缓存,灵活性要大多了。

  38. 老赵
    admin
    链接

    老赵 2010-06-04 10:09:38

    @沐枫

    是可以这么做,接下来一篇文章结尾我不说还有什么办法可以实现吗?其实我想的也是这种,呵呵。

    这么做的优点是灵活,包括方法调用都可以一并实现了。缺点是如果要做到性能高就比较麻烦,比现在生成动态类型的做法要麻烦不少。

  39. 沐枫
    120.32.59.*
    链接

    沐枫 2010-06-04 10:11:18

    用 type.GetProperty(binder.Name) 可能会有些问题。不过总体思路个人感觉要比楼主完全复制一个实体要好一点。

  40. 沐枫
    120.32.59.*
    链接

    沐枫 2010-06-04 13:09:24

    如果是在非asp.net应用中,直接用InternalsVisibleToAttribute就可以访问internal类型了。

    可是在asp.net应用中,aspx等视图页面是动态编译生成的,生成的dll名称是随机的,这样就无法使用InternalsVisibleToAttribute了。

    不知道InternalsVisibleToAttribute是如何工作的。

  41. 老赵
    admin
    链接

    老赵 2010-06-04 13:49:46

    @沐枫

    让CLR或代码去检查这个Attribute不就行了,呵呵。

  42. 沐枫
    124.72.42.*
    链接

    沐枫 2010-06-10 12:25:32

    不知道有没有办法,让mvc视图编译时给一个固定的名称,以便用 InternalsVisibleToAttribute 去绑定?

  43. 沐枫
    124.72.42.*
    链接

    沐枫 2010-06-10 12:50:20

    另外,mvc还有一个不小的缺点,就是视图没有提供批量编译的功能,常常有视图代码在拼写错误或其它异常时,出现黄页错误。 如果能批量编译,可省不少检查错误的时间。

  44. 老赵
    admin
    链接

    老赵 2010-06-10 13:44:35

    @沐枫: 另外,mvc还有一个不小的缺点,就是视图没有提供批量编译的功能,常常有视图代码在拼写错误或其它异常时,出现黄页错误。

    从来就是有的,还没有ASP.NET MVC的时候就有了。官方的模板里也提供了这个功能,直接用MSBuild编译就能检查了,不过默认是关闭的。

  45. 沐枫
    124.72.42.*
    链接

    沐枫 2010-06-11 12:00:41

    啊哈,谢谢啦。 我犯傻了。aspnet_compiler早先常用生成,居然就成定势了,也试都没想到要去试一下用在mvc做批编译。现在好了,我弄了个webdeployment,随时检查了。

  46. momo
    222.84.65.*
    链接

    momo 2010-08-26 16:40:55

    老赵,你的这个方法,有个问题。

    我在vs2010下面试了,LinQ To SQL 下没有问题,但是 LinQ To Entity 实体框架下,编译通过,但是运行时,页面报:LINQ to Entities 不识别方法“System.Object ToDynamic(System.Object)”,因此该方法无法转换为存储表达式。

  47. momo
    222.84.65.*
    链接

    momo 2010-08-26 16:46:49

    public class UserController : Controller
    {    
    
        //Linq to entity 生成的数据库上下文
        TestEntities _DB = new TestEntities();
    
        //Linq to sql 生成的数据库上下文
        DataClassesDataContext _db = new DataClassesDataContext();  
    
        public dynamic users_entity = new System.Dynamic.ExpandoObject();
        public dynamic users_sql = new System.Dynamic.ExpandoObject();
    
        public ActionResult Index()
        {
    
            users_entity = 
                from user in _DB.User
                select new
                {
                    user.description,
                    ID = user.ID, 
                    user.name,
                    user.password,
                    user.phone,
                    user.age
                }.ToDynamic();
    
            users_sql = 
                from user in _db.User
                select new
                {
                    user.description,
                    ID = user.ID, 
                    user.name,
                    user.password,
                    user.phone,
                    user.age
                }.ToDynamic();
    
            return View(users_entity); // 用Linq to Entity 查的数据,view页面运行时报错
            // return View(users_sql);  // 用Linq to SQL 查的数据,没有任何问题
        }
    }
    

    view 代码

    <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
    <% foreach (var item in Model) { %>
        <%:item.name %>
    <%} %>
    
  48. 老赵
    admin
    链接

    老赵 2010-08-27 00:28:13

    @momo

    这不是ToDynamic这方面的问题,而是你要分清LINQ to Object部分和Expression Tree部分。还有我很怀疑这段LINQ to SQL运行时能不能跑……我记得LINQ to SQL应该是不能直接Select成匿名对象才对,难道改过了?

    PS:你的代码格式真差,建议改进……

  49. momo
    113.12.217.*
    链接

    momo 2010-08-27 10:45:24

    linq to sql 直接select 匿名对象是可以运行的

    格式是贴到blog里,被弄乱了

    你要分清LINQ to Object部分和Expression Tree部分

    我才学疏浅,一时半会,还弄不懂。能否告诉我如何解决这个问题,还请明示

  50. 老赵
    admin
    链接

    老赵 2010-08-28 01:08:07

    @momo: 格式是贴到blog里,被弄乱了

    不光如此,代码格式本身也不佳,贴过来更加乱了,我准备了那么好的代码着色功能和实时预览,都被你忽视了,唉。

    关于LINQ to Object和Expression Tree的区别与联系,先找点资料吧,都是最基本的内容。

  51. 学习狂
    210.13.70.*
    链接

    学习狂 2011-12-06 01:39:55

    老赵的这个问题现在有什么最新进展吗?好像手头正好遇到,不知道后续的mvc3.0版本是否解决了这个问题?

  52. 学习狂
    210.13.70.*
    链接

    学习狂 2011-12-06 01:44:35

    突然想起,老赵的这篇博文是在1年半以前发的,那时候刚好参加了第一次技术交流会,一晃3届过去了。。不知道将来还有没有。。

  53. 学习学习
    119.62.48.*
    链接

    学习学习 2013-04-27 17:03:34

    这个问题什么时候微软能够解决呢?

  54. 老赵
    admin
    链接

    老赵 2013-04-27 22:59:56

    @学习学习

    这其实不是问题,事实上微软的做法更恰当,可惜在这里不方便。

  55. cbfjay
    120.198.155.*
    链接

    cbfjay 2013-06-07 15:44:37

    楼主,现在由更进一步的解决方法了吗?

  56. 链接

    陈相荣 2013-07-20 10:30:22

    匿名类型是internal的,我是这么干的,设置友元程序集

  57. dnawo
    36.251.45.*
    链接

    dnawo 2013-08-31 02:41:19

    .NET 4.0的System命名空间下有一个类Tuple,当匿名对象属性不大于8个时,可以用它来解决:

    // Controller
    public ActionResult Index()
    {
        testContext context = new testContext();
        dynamic data = context.People
            .Join(context.Pets, person => person.Id, Pet => Pet.Pid, (person, pet) => new { Person = person.Name, Pet = pet.Name })
            .ToList().Select(item => Tuple.Create(item.Person, item.Pet));
        return View(data);
    }
    
    // View
    @model IEnumerable<Tuple<string, string>>
    
    @foreach(var item in Model)
    {
        @(item.Item1)<text>,</text>@(item.Item2)<br />
    }
    
  58. 老赵
    admin
    链接

    老赵 2013-08-31 10:53:37

    @dnawo

    你不嫌记Item12345麻烦么……

  59. whwqs
    116.211.249.*
    链接

    whwqs 2013-12-14 19:20:52

    我用Newtonsoft解决的,控制器中形如:

    return View(Newtonsoft.Json.JsonConvert.DeserializeObject(Newtonsoft.Json.JsonConvert.SerializeObject(new { id = 3 })));
    

    Newtonsoft会把匿名类型转换成Newtonsoft.Json.Linq.JArray或者Newtonsoft.Json.Linq.JObject,从而变成强类型。

  60. zzdfc
    218.63.144.*
    链接

    zzdfc 2014-05-04 23:30:13

    过了这么久,现在有更好的解决方法吗?

    试过SmartObject吗?也是很方便的,不知道性能怎么样。http://smartobject.codeplex.com/

  61. link
    27.45.33.*
    链接

    link 2014-08-20 23:22:20

    看到了好多有趣的方法,我试着用下面这个方法也解决了问题, 就是不知道性能如何

    var re = Menus.Select(a =>
    {
        var d1 = new ViewDataDictionary();
        d1["Name"] = a.Name;
        d1["SubMenu"] = a.SubMenu;
        d1["ID"] = a.ID;
        return d1;
    });
    

    `

  62. link
    27.45.33.*
    链接

    link 2014-08-20 23:23:47

    唉, 太笨了, 怎么显示代码的都不会

  63. 手术床
    101.81.123.*
    链接

    手术床 2018-10-19 10:36:57

    手术床手术灯

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我