【问题标题】:C# Entity Framework - How to pass a value in the Where clause dynamicallyC# Entity Framework - 如何在 Where 子句中动态传递值
【发布时间】:2019-12-28 10:17:30
【问题描述】:

我正在尝试过滤掉表中的行。

我有以下代码:

if (groupRadio.Checked)
        {
            using (gf = new GFilterEntities())
            {
                dgvFiltered.DataSource = gf.GroupTs.Where(x => x.G_Platform.Equals(platformCombo.Text) &&
                x.G_Type.Equals(typeCombo.Text) && x.fieldNameCombo.Text <= how to do this

            }
        }

我想从 x.fieldNameCombo.Text 中的 ComboBox 动态传递列名,但我无法这样做。如何做到这一点?

示例:G_Platform 和 G_Type 是 GroupT 表中的列名。当我输入 x。我可以看到这些值填充在 Visual Studio 中。

但是,我希望能够使用用户在组合框中输入的值进行过滤。所以类似于 x.fieldNameComboBox 中的用户值.Equals(textBox1.text)。 fieldNameComboBox 值将是一个列名,就像 G_Platform 和 G_Type 一样。

谢谢。

更新 基于下面的 cmets 之一(由 sam 编写),我的代码现在显示为:

 private void btnSearch_Click(object sender, EventArgs e)
    {
        if (groupRadio.Checked)
        {
            using (gf = new GFilterEntities())
            {
                var testClass = new TestClass { ComboBoxSelectedText = fieldNameCombo.Text, UserInputValue = txt1.Text};
                var comboBoxSelectedColumnValues = from object item in gf.GroupTs
                                                   let propertyInfo = item.GetType().GetProperties()
                                                   from info in propertyInfo
                                                   where string.Equals(info.Name, testClass.ComboBoxSelectedText)
                                                   select (string)info.GetValue(item)
                                                   into s
                                                   where !string.IsNullOrWhiteSpace(s)
                                                   select s;


                dgvFiltered.DataSource = gf.GroupTs.Where(x => x.G_Platform.Equals(platformCombo.Text) &&
                x.G_Type.Equals(typeCombo.Text) && comboBoxSelectedColumnValues.Equals(testClass.UserInputValue)
                ).ToList();
            }
        }
    }
    public class TestClass
    {
        public string ComboBoxSelectedText { get; set; }
        public string UserInputValue { get; set; }
    }

我收到一条错误消息: System.NotSupportedException:“无法将类型“Filtering.GroupT”转换为类型“System.Object”。 LINQ to Entities 仅支持转换 EDM 基元或枚举类型。'

【问题讨论】:

  • 如何获取用户输入值?你能举一两个例子吗?因为您使用的是
  • 您可以在组合框值上执行 switch 或 if-else if 逻辑以确定要包含的过滤器。
  • 这是一个 Windows 窗体应用程序。我从表单中的 ComboBox 获取用户输入。当我输入 x。我得到了表中所有列名的列表。如何使用 ComboBox 中的文本来获取列名,而不是从我从 x 获得的下拉列表中选择。
  • 你和表单在同一个班级里跑吗?要获得组合框,您需要使用表单的实例。您可能还需要进入 design.cs 文件并公开变量。
  • @jdweng 是的。我和表格在同一个班级里跑步。问题是从上面组合框中的用户输入动态获取列属性(我添加了图片),而不是从我得到的列表中预先选择(还添加了上面的代码图片)

标签: c# datagridview


【解决方案1】:

当我们在查询中需要类似 LINQ 并不真正支持的“动态”内容时,我们使用 EF Core 的“原始 SQL 查询”功能,此处解释:https://docs.microsoft.com/en-us/ef/core/querying/raw-sql

所以你可以这样写:

var whereStatement = $"G_Platform = '{platformCombo.Text}' AND {fieldNameCombo.Text} = '{textBox1.text}'"; // generate this based on your user's input
dgvFiltered.DataSource = gf.GroupTs
                           .FromSqlRaw($"SELECT * FROM GroupTs WHERE {whereStatement}")
                           .ToList();

几个笔记:

  • 在 EFCore 3.0 中更改了函数名称 - 在以前的 EFCore 版本中,它被称为 FromSql 而不是 FromSqlRaw
  • 在您的最终版本中,请确保您遵守文档中关于对原始 SQL 查询使用参数化的警告:

在将任何用户提供的值引入原始 SQL 查询时,必须小心避免 SQL 注入攻击。除了验证此类值不包含无效字符外,请始终使用参数化,将值与 SQL 文本分开发送。

特别是,切勿将带有未经验证的用户提供值的连接或插值字符串 ($"") 传递到 FromSqlRaw 或 ExecuteSqlRaw。 FromSqlInterpolated 和 ExecuteSqlInterpolated 方法允许以防止 SQL 注入攻击的方式使用字符串插值语法。

【讨论】:

  • 我可能不明白,但是我怎样才能动态地得到你提到的列名呢?你说:var whereStatement = $"G_Platform = '{platformCombo.Text}'" 和 G_Platform 是列名。我想做的是: var whereStatement = user inputed column name via combobox has value in textBox1.Text
  • @Raza156 ​​你可以这样写:var whereStatement = $"{fieldNameCombo.Text} = '{textBox1.text}'";。我更新了我的答案以包括这个。
【解决方案2】:

就像提到的 ajphall 一样,取决于您的 EF 版本,您可以将 FromSqlRaw 用于 EF Core 或 FromSql 用于旧版本,在您的情况下,请使用 FromSqlInterpolated 或 sql 参数来防止 sql 注入文档:https://docs.microsoft.com/en-us/ef/core/querying/raw-sql

要解决您的查询问题,您可以执行以下操作:

var plataformParameter = platformCombo.Text;
var typeParameter = typeCombo.Text
var fieldParameter = fieldNameCombo.Text;   
dgvFiltered.DataSource = gf.GroupTs
                         .FromSqlInterpolated(
                                              $"SELECT * FROM GroupTs WHERE
                                              G_Platform = {plataformParameter } AND 
                                              G_Type = {typeParameter} AND 
                                              (G_Name = {fieldParameter} 
                                              OR G_Owner = {fieldParameter}")
                                              .ToList()

根据你的数据,如果你不想返回空值,你可以先检查一下。

...((G_Name<>null and G_Name={fieldParameter})or (G_Owner <> null and G_Owner={fieldParameter}))

希望这会有所帮助。

更新:

您可以使用动态 SQL,但请注意您必须验证输入,否则您最终会遇到 sql 注入:

  1. 创建存储过程
  2. 添加您要作为过滤依据的所有参数。
  3. 从您的代码中调用此存储过程并传递您需要的参数。

    --This is just and example I haven't run this query.
    DECLARE @platform VARCHAR(20)
    DECLARE @columnName VARCHAR(20) -- this should be the name from the input
    DECLARE @columnValue VARCHAR(20)--
    DECLARE @typeValue VARCHAR(20)
    DECLARE @sqlCommand VARCHAR(255)
    
    --DO SOME CHECKS TO ENSURE THE COLUMN ACTUALLY EXISTS.
    --DO NOT JUST RUN WHAT YOU GOT FROM THE INPUT
    SET @sqlCommand = 'SELECT * FROM GroupTs WHERE G_PLATFORM=' + @platform +'AND G_Type='+@typeValue+'AND'+@columnName+'='+@columnValue
    
    EXEC (@sqlCommand)
    

这里有更多关于这个https://www.essentialsql.com/build-dynamic-sql-stored-procedure/的信息

【讨论】:

  • 当你说:AND (G_Name = {fieldParameter} OR G_Owner = {fieldParameter}") 如果我有 30 列可能效率不高。 G_Platform 和 G_Type 有各自的组合框,所以它们无关紧要。 G_Name 和 G_Owner 可以从称为 fieldNameCombo 的通用组合框中选择。我想要的是,如果用户在 fieldNameCombo 中选择“G_Name”,并在文本框中输入一个值,则该表应按 G_Name 列中的该值以及 G_Platform 和 G_Type 进行过滤。这可以动态完成而不是使用多个 OR 吗?
  • 看看我更新的答案,不确定是不是你要找的东西
【解决方案3】:

非常感谢您的回复。我发现以下解决方案是最佳的:

我创建了一些静态帮助方法,每个方法都返回一个 Lambda 表达式。例如,对于“.Contains”,我添加了以下方法:

private static Expression<Func<T, bool>> GetContainsExpression<T>(string propertyName, string propertyValue)
    {
        var parameterExp = Expression.Parameter(typeof(T), "type");
        var propertyExp = Expression.Property(parameterExp, propertyName);
        MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
        var someValue = Expression.Constant(propertyValue, typeof(string));
        var containsMethodExp = Expression.Call(propertyExp, method, someValue);

        return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
    }

对于 .StartsWith 方法如下(类似):

private static Expression<Func<T, bool>> GetStartsWithExpression<T>(string propertyName, string propertyValue)
    {
        var parameterExp = Expression.Parameter(typeof(T), "type");
        var propertyExp = Expression.Property(parameterExp, propertyName);
        MethodInfo method = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
        var someValue = Expression.Constant(propertyValue, typeof(string));
        var containsMethodExp = Expression.Call(propertyExp, method, someValue);

        return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
    }

“.Equals”和“.EndsWith”也是如此。

后来,在我上面的点击方法中(在我的问题中),我添加了以下 lambda 表达式: (这个是 .Contains 的)

if (valCombo.Text.Equals("contains"))
                        {
                            dgvFiltered.DataSource = af.Accounts.Where(GetContainsExpression<Account>(fieldNameCombo.Text, txt1.Text))
                                .Where(x => x.A_Platform.Equals(platformCombo.Text) && x.A_Type.Equals(typeCombo.Text)).ToList();
                        }

这个是 .StartsWith:

if (valCombo.Text.Equals("starts with"))
                        {
                            dgvFiltered.DataSource = af.Accounts.Where(GetStartsWithExpression<Account>(fieldNameCombo.Text, txt1.Text))
                                .Where(x => x.A_Platform.Equals(platformCombo.Text) && x.A_Type.Equals(typeCombo.Text)).ToList();
                        }

等等。是的,它确实使每个代码示例中的 .Wheres 都位于 && 表达式之上。所以,为了解决 ||在一个 Where 为真而另一个不是的情况下,我在它们上方添加了以下 if 语句:

if (fieldNameCombo.Text == "")
                    {
                        dgvFiltered.DataSource = af.Accounts.Where(x => x.A_Platform.Equals(platformCombo.Text) && x.A_Type.Equals(typeCombo.Text)).ToList();
                    }

希望这可以澄清这个问题。如果您有任何问题,请告诉我。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-11
    • 1970-01-01
    • 1970-01-01
    • 2016-11-26
    • 2019-01-26
    • 1970-01-01
    相关资源
    最近更新 更多