【问题标题】:Pass in an Expression to linq's Select将表达式传递给 linq 的 Select
【发布时间】:2012-04-02 09:31:28
【问题描述】:

这是 linq-to-sql

我有很多不同的类都在执行相同的查询,但投影结果略有不同。理想情况下,我希望能够将查询放在一个地方,并将投影传递给 Select 方法。它适用于具体类型:

public void GetResults() {
    var junk = db.SiteProducts.Select(Project());
}

public Expression<Func<DbEntities.SiteProduct, string>> Project() {
    return p => p.ProductAlert;
}

但是当我尝试返回一个匿名类型时,它失败了

public void GetResults() {
    var junk = db.SiteProducts.Select(Project());
}

public Expression<Func<DbEntities.SiteProduct, TResult>> Project<TResult>() {
    return p => new { p.ProductAlert };
}

我完全理解为什么泛型类型推断在第二种情况下会失败。但是有没有什么技巧——除了从头开始制作我自己的表达式——我错过了可以让它工作的技巧?

【问题讨论】:

标签: c# linq linq-to-sql devforce


【解决方案1】:

这是一个有趣的问题。我认为 DTO 可以在这里为您提供帮助,但是需要注意一些限制和陷阱。以下面的 LINQPad 为例:

class ProjectDTO
{
    public string Name { get; set; }

    public static Expression<Func<Project, ProjectDTO>> ToDTO = (e) => new ProjectDTO
    {
        Name = e.Name
    };

    public ProjectDTO() {}

    public ProjectDTO(Project project)
    {
        Name = project.Name;
    }
}

void Main()
{
    Projects.Select(p => p.Name).Dump();
    Projects.Select(ProjectDTO.ToDTO).Dump();
    Projects.Select(p => new ProjectDTO(p)).Dump();
}

SQL 生成:

SELECT [t0].[Name]
FROM [Project] AS [t0]
GO

SELECT [t0].[Name]
FROM [Project] AS [t0]
GO

SELECT [t0].[ProjectId], [t0].[Name], [t0].[Description], [t0].[DateCreated], [t0].[DateModified], [t0].[DateComplete], [t0].[CreatedBy]
FROM [Project] AS [t0]

如您所见,您不能使用复制构造函数来分配 DTO 的属性,因为这会强制从数据库中拉回整个对象。

如果您想扩展基本 DTO 并为更专业的数据视图添加更多属性,这也会略有限制,这意味着您最终可能会得到多个具有相似代码的表达式。

但是,我非常喜欢选项二,但我确信此选项很可能仅限于单一类型的投影,请考虑以下示例:

var query = from p in Projects
            join t in Tasks on p.ProjectId equals t.ProjectId
            select ProjectDTO.ToDTO; //Can't be used like this

我认为您不能在这种类型的查询语法中使用表达式。一般来说,我认为不会有一个全面有效的解决方案。您可能需要检查您的设计,看看是否可以提供更少的预测,因为某些属性非常便宜,始终包含在查询中?

不使用动态 LINQ 库或手动构建表达式树,我还想看看是否可以使用 LINQ-SQL/LINQ-Entities 创建动态选择。

【讨论】:

    【解决方案2】:

    如果我正确理解您的问题,您可以使用以下代码:

    首先声明一个选择数据的方法,如下所示:

    public List<TResult> FindAll<TResult>(Func<Regions, TResult> selector) where TResult : class
        {
            using (RepositoryDataContext = new DataClasses1DataContext())
            {
                    return RepositoryDataContext.Regions.Select<Regions, TResult>(selector).ToList<TResult>();
    
            }
        }
    

    然后您可以像这样构建您的选择语句:

    Func<Regions, SelectAllRegion> select = r => new SelectAllRegion
            {
                RegionID = r.RegionID,
                RegionDescription = r.RegionDescription
            };
    

    我的SelectAllRegion:

     public class SelectAllRegion
    {
        public SelectAllRegion()
        {
        }
        public int RegionID { get; set; }
        public string RegionDescription { get; set; }
    }
    

    地区是Region北翼的桌子。我希望这对你有帮助

    【讨论】:

      【解决方案3】:

      IdeaBlade 有一个ProjectionSelector 类,您可以使用它来抽象您的投影。当需要构造投影查询,但编译时不知道涉及的类型时,可以创建ProjectionSelector类的实例,并在运行时传入类型信息。

      类和示例代码可以在这里找到:

      创建动态的“Select”、“SelectMany”和“GroupBy”子句
      http://drc.ideablade.com/xwiki/bin/view/Documentation/dynamic-projection

      【讨论】:

      • 谢谢罗伯特。这个项目的存在让我相信没有任何语法糖可以让它按原样工作,但为了以防万一,我会把它放得更开一点。
      • 他们可能在引擎盖下发射 IL。这就是他们使匿名类型类在运行时工作的方式。
      • 还有一段迷人的代码:msdn.microsoft.com/en-us/vstudio/bb737920#dynsel
      • 当然 - 那个 msdn 链接它只是手动构建一个表达式树。可能是 IdeaBlade 项目正在做什么。
      【解决方案4】:

      这在编译时不起作用。当然,使用动态的东西你可以让它工作。

      一个简单的解决方案是不使用匿名类型,而是使用定制的 DTO 类。这样的 DTO 类只需要很少的行,并且易于维护。通常这是一个很好的解决方案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-02-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多