65.9K
CodeProject 正在变化。 阅读更多。
Home

将匿名类型转换为任何类型

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (23投票s)

2009年8月1日

CPOL

5分钟阅读

viewsIcon

351698

如何将 LINQ to SQL 的匿名类型转换为指定类型。

引言

本文介绍如何使用 .NET 3.5 扩展来处理从匿名类型到特定类型的转换。这在使用 LINQ to SQL 以列表和/或数组的形式检索数据时尤其有用。

问题

通过 Microsoft LINQ to SQL,您可以在享受强类型编程语言的同时,还能掌控您的数据。数据对象由类表示,当您通过 DBML 文件将数据结构链接到您的 Visual Studio 项目时,这些类会自动创建。

现在,您可以直接在 Visual Studio 编程环境中编写数据检索过程,而无需在 SQL 和编程之间分心。

同时,LINQ to SQL 有时也会带来一些挑战。其中之一就是 LINQ to SQL 查询返回的 匿名 类型。当您在一个查询中连接多个数据库表时,您必须选择如何组织返回的序列。很多时候,您需要创建一个返回匿名类型对象列表或数组的序列。许多程序员,包括我自己在内,认为在业务和/或 UI 层操作此类匿名类型对象并非最佳实践。

解决方案

假设我们有一个包含两个表的数据库:*Client* 和 *Order*。

Client 表包含客户特定信息。在本例中,是 NameAddressID,后者显然指向某个存储该客户地址信息的仓库,但目前这不是我们的关注点。Order 表包含描述订单的文本(字符串格式)以及一个 *ClientID* 列,该列指向下订单的客户的 ID。

假设我们想获取所有客户的信息以及每个客户的订单数量。

让我们通过运行以下查询来查看数据

Group By 查询返回一个包含 ID、Name、Address ID 和 Total Orders 的行集,适用于 *Client* 表中的所有客户。在过去,您会使用 ADO.NET 来检索和处理数据。

解决问题

如果您想使用 LINQ to SQL,您会创建一个过程来检索包含所需信息的序列

public static List<Client> GetClientsWithTotalOrders()
{ 
   //create DataContext object, use it, discard it:
    using (CodeProjectDataContext db = new GetDataContext())
    {
        //join Client with Order:
        var res = db.Clients
            .Join(
            db.Orders,
            o => o.ID,
            c => c.ClientID,
            (c, o) => new { c }
            )

            //group by Client
            .GroupBy(o => o.c)

            //define output object
            .Select(o => new { ID = o.Key.ID, AddressID = o.Key.AddressID, 
            Name = o.Key.Name, TotalOrders = o.Count() })
            
            //output to List of objects:
            .ToList()
            ;

         //cast the output sequence to List of Clients and return:
         return (List<Client>)res;
    }
}

在此过程中,我们连接了 *Client* 和 *Order* 表,并按 *Client* 分组,计算了每个客户下了多少订单。结果返回格式如下:ID、Address ID、Name 和 Total Orders。

利用创建部分类的能力,我们在 Client 类中添加一个属性,并将我们想要的任何属性/方法添加到由 Visual Studio 自动设计器生成的类中。

public int TotalOrders { get; set; }

现在,Client 类具有以下属性:IDNameAddressIDTotalOrders。LINQ to SQL GetClientsWithTotalOrders() 过程返回一个包含所有这些属性的对象列表。

但是,如果您尝试编译该过程,您会收到一个错误

Error    1    Cannot convert type 'System.Collections.Generic.List<AnonymousType#1>' 
   to 'System.Collections.Generic.List<CodeProject.LinkToSql.Client>'    
   C:\Development\VS2008\Projects\CodeProject\CodeProject\LinkToSql\DbHandlers.cs
       38    23    CodeProject

不幸的是,编译器无法识别程序创建的匿名类型与您的 Client 类具有相同的属性集。这意味着您在检索数据后将不得不处理匿名 List 类型。我们如何将此匿名类型转换为 Client 类型?

通过编写 扩展 来处理匿名类型对象。

我创建了两个扩展来处理这个问题

public static object ToType<T>(this object obj, T type)
{

    //create instance of T type object:
    var tmp = Activator.CreateInstance(Type.GetType(type.ToString())); 

    //loop through the properties of the object you want to covert:          
    foreach (PropertyInfo pi in obj.GetType().GetProperties()
    {
      try 
      {   

        //get the value of property and try 
        //to assign it to the property of T type object:
        tmp.GetType().GetProperty(pi.Name).SetValue(tmp, 
                                  pi.GetValue(obj, null), null)
      }
      catch { }
     }  

   //return the T type object:         
   return tmp; 
}

此扩展允许将匿名类型对象转换为指定类型。如果您有一个匿名类型的对象并想将其转换为 Client 类型,则需要调用此扩展。

object obj=getSomeObjectOfAnonymoustype();
Client client=obj.ToType(typeof (Client));

让我们看看它是如何工作的

首先,它使用 Activator.CreateInstance() 过程创建一个空的临时 Client 类型对象。然后,它遍历调用对象的所有属性信息,获取其值,并将该值重新分配给新创建的 Client 对象的相应属性。最后,它返回一个属性已从调用对象填充的 Client 类型对象。

因此,对于单个对象,问题已解决。

那么对于这种对象的列表呢?或者数组呢?

我创建的第二个扩展是将一个 匿名 类型对象的 List 转换为特定类型对象的 List

public static object ToNonAnonymousList<T>(this List<T> list, Type t)
{

   //define system Type representing List of objects of T type:
   var genericType = typeof(List<>).MakeGenericType(t);

   //create an object instance of defined type:
   var l = Activator.CreateInstance(genericType);

   //get method Add from from the list:
   MethodInfo addMethod = l.GetType().GetMethod("Add");

   //loop through the calling list:
   foreach (T item in list)
   {

      //convert each object of the list into T object 
      //by calling extension ToType<T>()
      //Add this object to newly created list:
      addMethod.Invoke(l, new object[] { item.ToType(t) });
   }

   //return List of T objects:
   return l;
}

上面代码的第一行相当有趣

var genericType = typeof(List<>).MakeGenericType(t);

当我们在 typeof(List<>) 上调用 MakeGenericType(t) 函数时,它会用 List 对象的类型 T 替换,并返回一个表示 T 对象列表的 Type 对象。

在此之后,一切都变得非常简单

Activator 创建一个空的 T 对象列表。GetType().GetMethod("Add") 返回 MethodInfo 对象,我们将使用它来调用新创建列表的 Add() 方法。

然后,我们遍历原始列表,通过调用我们自己的扩展 ToType<T>() 将每个元素的原始类型更改为 T,最后将此项添加到类型为 T 的列表中。返回的结果是类型为 T 的列表。

让我们用这个新扩展来更新过程。

public static List<Client> GetClientsWithTotalOrders()
{
    //create DataContext object, use it, discard it:
    using (CodeProjectDataContext db = new GetDataContext())
    {
        //join Client with Order:
        var res = db.Clients
            .Join(
            db.Orders,
            o => o.ID,
            c => c.ClientID,
            (c, o) => new { c }
            )

            //group by Client
            .GroupBy(o => o.c)

            //define output object
            .Select(o => new { ID = o.Key.ID, AddressID = o.Key.AddressID, 
            Name = o.Key.Name, TotalOrders = o.Count() })
            
            //output to List of objects:
            .ToList()

            //apply extension  to covert into Client
            .ToNonAnonymousList(typeof(Client))
            ;

         //cast the output sequence to List of Clients and return:
         return (List<Client>)res;
    }
}

调用 ToNonAnonymousList(typeof(Client)) 将匿名类型列表转换为 Client 类型列表。

历史

  • 发布于 2009 年 8 月 2 日
  • 当我发布这篇文章时,我收到了一些评论,询问为什么我要通过调用这些扩展来转换匿名类型,而我们可以直接使用以下语法。

    .Select(o => new Client { ID = o.Key.ID, AddressID = o.Key.AddressID, 
       Name = o.Key.Name, TotalOrders = o.Count() })

    正如您可能注意到的,这个语句创建了一个 Client 对象而不是一个匿名对象。

    如果您在论坛上搜索,您会发现许多开发人员抱怨这种语法对许多 LINQ to SQL 查询不起作用,并且会返回以下错误。

    "Explicit construction of entity type 'xxxxxxxx' in query is not allowed"

    此错误是由于 Microsoft 添加的一项检查。“添加此检查是因为它本应从一开始就存在但却缺失了。手动构造实体实例作为投影会将可能格式错误的对象的缓存弄乱,导致程序员困惑并给我们带来大量错误报告。此外,投影出的实体是否应放入缓存或是否应被跟踪也是模糊不清的。实体的使用模式是它们在查询外部创建,并通过 DataContext 插入到表中,然后在稍后通过查询检索,绝不会由查询创建”。

© . All rights reserved.