【问题标题】:Linq To SQL Select Dynamic ColumnsLinq To SQL 选择动态列
【发布时间】:2012-10-09 17:16:02
【问题描述】:

是否可以动态限制从 LINQ to SQL 查询返回的列数?

我有一个超过 50 列的数据库 SQL 视图。我的应用程序有一个具有 50 多个属性的域对象,每列一个。在我的 winforms 项目中,我将域对象列表绑定到网格。默认情况下,只有少数列可见,但用户可以打开/关闭任何列。

用户抱怨网格加载时间过长。我捕获了 LINQ 生成的 SQL 查询,然后在 SQL Server Management Studio 中执行它并验证了它的缓慢性。如果我更改 SQL 语句,删除所有不可见的列,它几乎会立即运行。性能与查询中的列数直接相关。

我想知道是否可以动态更改从 LINQ 生成的 SQL 查询返回的列数?例如,这是我的代码目前的样子:

public List<Entity> GetEntities()
{
    using (var context = new CensusEntities())
    {
        return (from e in context.Entities
            select e).ToList();
    }
}

context.Entities 对象是从包含超过 50 列的 SQL 视图生成的,因此当执行上述操作时,它会生成类似“SELECT Col1, Col2, Col3, ... Col50 FROM Entity INNER JOIN...”的 SQL。我想将方法​​签名更改为如下所示:

public List<Entity> GetEntities(string[] visibleColumns)
{
    using (var context = new CensusEntities())
    {
        return (from e in context.Entities
            select e).ToList();
    }
}

我不知道如何改变这个方法的主体来改变生成的SQL语句只返回我关心的列值,其他的都可以是NULL。

【问题讨论】:

  • 必须是LINQ2SQL吗?您可以改用动态 sql:将您自己的 sql 语句构建为字符串,然后将其作为命令传递
  • 我曾考虑过使用动态 sql,但真的希望使用 LINQ 找到一个好的解决方案。我们已经编写了一个扩展方法,它采用 IQueryable 并使用表达式树应用基于可见列数组的过滤器。它很棒,因为我可以在执行 LINQ to SQL 或 LINQ to Objects 时重新使用扩展方法。该框架足够智能,可以生成 SQL WHERE 子句或仅在内存中应用过滤器。同样,如果有一个可以利用 LINQ 且不需要我对 SQL 语法有任何了解的解决方案,那就太好了
  • 你有没有找到一个好方法来做到这一点?

标签: c# sql linq


【解决方案1】:

这样的事情应该可以工作:

 List<string> columns = new List<string>();
 columns.Add("EmployeeID");
 columns.Add("HireDate");
 columns.Add("City");

将列添加到您的列表中^。

var result = Class.ReturnList(columns);  

将列表传递给方法^。

public static List<Entity> ReturnList(List<string> VisibleColumns)
        {
            StringBuilder SqlStatement = new StringBuilder();
            SqlStatement.Append("Select ");
            for (int i = 0; i < VisibleColumns.Count; i++)
            {
                if (i == VisibleColumns.Count - 1)
                {
                    SqlStatement.Append(VisibleColumns[i]);
                }
                else
                {
                    SqlStatement.Append(VisibleColumns[i]);
                    SqlStatement.Append(",");
                }
            }
            SqlStatement.Append(" FROM Entity");
            using (var ctx = new DataClasses1DataContext())
            {
                var result = ctx.ExecuteQuery<Entity>(SqlStatement.ToString());
                return result.ToList();
            }

        }

这基本上只是用VisibleColumns 列表中传入的所有字段创建一个SELECT 语句。

此时VisibleColumns列表中的字符串将生成的SQL语句为:

Select EmployeeID, HireDate, City From Employee

(注意:我使用 Northwind 数据库进行了尝试,因此使用了 EmployeeID 等列名。显然,您应该将它们替换为您自己的。)

【讨论】:

  • 谢谢!我会试一试。
  • 让我发布!:) 它对我有用,所以..我希望你也能让它工作
  • 如果我没有在查询的 SELECT 部分中包含所有预期的列,我会收到数据读取器错误。例如,如果在查询中只传递了“First”列,则看起来像“SELECT First FROM Entity”。执行时会生成一个错误,指出 datareader 预期列“ID”,但在结果集中找不到。
  • 我也有这个问题,尝试添加 ID 列。那么它应该可以工作..我不知道为什么会发生这种情况。
  • 添加“ID”列后,我收到有关“Last”列的错误。如果我添加“最后一个”,我会收到一个关于“中间”的错误,如果其他列丢失,这种情况会继续。我尝试使用反射来动态添加所有不可见的列,并将每个列别名为 NULL 值。对于 ex 查询看起来像“SELECT ID, First, NULL 'Last', NULL 'Middle', NULL 'Birthday'... FROM Entity”问题是几个列不可为空。我可以在生成查询之前检查类型,如果不能为空,则使用类型默认值,但它会变成代码异味。
【解决方案2】:

动态执行此操作并非易事,但如果您想要检索的列组合有限,则可以执行如下显式选择:

public List<Entity> GetEntities()
{
    using (var context = new CensusEntities())
    {
        return (from e in context.Entities
            select new
            {
                col1 = e.col1,
                col4 = e.col4,
                col5 = e.col5,
            }
        ).ToList()
        .Select(x=>new Entity{col1 = x.col1, col4 = x.col4, col5 = x.col5}).ToList();
    }
}

额外的选择步骤是必要的,因为 LINQ2SQL 不会为您创建部分实体。

为用户想要检索的每个常见的列组合(尤其是初始列)创建一个方法。

但是,要使这种动态化,您可以使用存储为匿名类中的属性的实体构建查询,并在同一匿名类的第二个属性中收集另一个匿名类中的结果属性。最后,您从收集的对象中选择您的实体到正确类型的对象中。

public List<Entity> GetEntities()
{
    using (var context = new CensusEntities())
    {
        var combinedResult = (from e in context.Entities
            select new {
                Entity = e,
                CollectedValues = new
                                  {
                                      // Insert default values of the correct type as placeholders
                                      col1 = 0, // or "" for string or false for bool
                                      col2 = 0, // or "" for string or false for bool
                                      // ...
                                      col49 = 0, // or "" for string or false for bool
                                      col50 = 0, // or "" for string or false for bool
                                  }
        );

        // Then copy each requested property

        // col1
        if (useCol1)
        {
            var combinedResult = (from e in combinedResult
                select new {
                    Entity = e,
                    CollectedValues = new
                                      {
                                          col1 = e.Enitity.col1, // <-- here we update with the real value
                                          col2 = e.CollectedValues.col2, // <-- here we just use any previous value
                                          // ...
                                          col49 = e.CollectedValues.col49, // <-- here we just use any previous value
                                          col50 = e.CollectedValues.col50, // <-- here we just use any previous value                                          }
            );
        }

        // col2
        if (useCol2)
        {
         // same as last time
                                          col1 = e.CollectedValues.col1, // <-- here we just use any previous value
                                          col2 = e.Enitity.col2, // <-- here we update with the real value
                                          // ...
        }

        // repeat for all columns, update the column you want to fetch

        // Just get the collected objects, discard the temporary
        // Entity property. When the query is executed here only
        // The properties we actually have used from the Entity object
        // will be fetched from the database and mapped.
        return combinedResult.Select(x => x.CollectedValues).ToList()
        .Select(x=>new Entity{col1 = x.col1, col2 = x.col2, ... col50 = x.col50}).ToList();
    }
}

会有很多代码,维护起来很痛苦,但它应该可以工作。
如果您要走这条路,我建议您构建一个代码生成器,该生成器通过您的 LINQ 上下文的反射构建此代码。

【讨论】:

  • 嗯,50 列将是大约 2500 行代码。在我只有两列的玩具示例中,它看起来更可行。
【解决方案3】:

试试这样的

using (var context = new CensusEntities())
{
    var q = from e in context.Entities
        select e.myfield1,e.myfield2;
    return q.Tolist();
}

生成的查询应该更轻,并且所有的数据转换都在下面。

但如果你真的需要构建动态输入,我认为应该涉及一些动态sql。所以

  1. 构建动态 SQL 并获取数据表
  2. 使用数据表到动态对象的转换,如下所示 How can I convert a DataTable into a Dynamic object?

顺便说一句,辛苦了,我认为你应该考虑使用第一块代码。

【讨论】:

  • @Thousand:听起来 OP 提出了错误的问题。如果他们只需要选择实际列的子集,这正是他们所需要的。 “动态”表示他们需要为请求一组不可预测的列的请求提供服务。
  • 您需要在您的选择中新建一个匿名类型才能使其正常工作
  • @MikeBantegui OP 正在传入一个字符串数组,我不知道他是否打算始终传入具有相同列的同一个数组,但我认为不会。
  • @千:如果列总是相同的,那么动态是完全没有必要的。 OP 需要回答以下问题:他们是否在不同调用中使用不可预测且不同的列集发出请求。
  • @eloycm,它在问题中明确表示“但是用户可以打开/关闭任何列”。
猜你喜欢
  • 2016-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多