public ActionResult test(string a="", string search = "")
{
...
var r = from c in db.n
select new {
c.id,
c.projectname,
c.type,
c.name};
if (search!="")
{
r = r.Where(x => x.a.Contains(search));
}
...
}
以上為預期想要的目標
可是實際上a的值就算是r中的值也無法使用
請問該如何修正呢?
研究後找到三個解法,提供給你。
//測試資料
Id | a | b
----|-----|-----
1 | 123 | abc
2 | 456 | def
var column = "a";
var value = "12";
var test = await _db.Tests
.FromSql($"SELECT * FROM Test WHERE {column} LIKE @value",
new SqlParameter("value", $"%{value}%"))
.Select(it => new
{
it.a,
it.b
})
.ToListAsync();
//結果: [{"a":"123","b":"abc"}]
exec sp_executesql N'SELECT [it].[a], [it].[b]
FROM (
SELECT * FROM Test WHERE a LIKE @value
) AS [it]',N'@value nvarchar(4)',@value=N'%12%'
go
使用文章中的 Search 函數
var column = "a";
var value = "12";
var test = await _db.Tests
.Search(column, value)
.Select(it => new
{
it.a,
it.b
})
.ToListAsync();
//結果: [{"a":"123","b":"abc"}]
SELECT [item].[a], [item].[b]
FROM [Test] AS [item]
WHERE [item].[a] LIKE N'%12%'
既然是想產生 .Where(it => it.a.Contains(value))
這段語法
我們就直接做出這段的運算式樹就好了。 (́◕◞౪◟◕‵)*
新增擴充方法
public static IQueryable<T> WhereContains<T>(this IQueryable<T> source,
string column, string value)
{
//取得 T.column 屬性
var property = typeof(T).GetProperty(column);
//產生 it.a 的 it
var itParameter = Expression.Parameter(typeof(T), "it");
//產生 it.a
Expression expressionProperty = Expression.Property(
itParameter, property.Name);
//產生 string.Contains 函數
var containsMethod = typeof(string)
.GetMethod("Contains", new[] { typeof(string) });
//產生 it.a.Contains(value)
var selector = Expression.Call(
expressionProperty,
containsMethod,
Expression.Constant(value));
//產生 .Where(it => it.a.Contains(value))
return source.Where(Expression.Lambda<Func<T, bool>>(selector, itParameter));
}
呼叫 WhereContains 函數就能產生 .Where(it => it.a.Contains(value))
的語法
var column = "a";
var value = "12";
var test = await _db.Tests
.WhereContains(column, value)
.Select(it => new
{
it.a,
it.b
})
.ToListAsync();
//結果: [{"a":"123","b":"abc"}]
SELECT [it].[a], [it].[b]
FROM [Test] AS [it]
WHERE CHARINDEX(N'12', [it].[a]) > 0
比較一下 EF 原生語法
var value = "12";
var test = await _db.Tests
.Where(it => it.a.Contains(value))
.Select(it => new
{
it.a,
it.b
})
.ToListAsync();
exec sp_executesql N'SELECT [it].[a], [it].[b]
FROM [Test] AS [it]
WHERE (CHARINDEX(@__value_0, [it].[a]) > 0) OR (@__value_0 = N'''')',N'@__value_0 nvarchar(4000)',@__value_0=N'12'
可以發現原生語法都有使用 sp_executesql
而我們使用運算式樹產生的語法則沒有
所以需要考慮一下安全性
目前不知道如何在運算式樹呼叫 sp_executesql
感覺是一個很深的坑
小弟目前的研究還無法填滿
所以就先這樣摟
我使用的版本是 EF Core 2.2.6
找到解法了,參考這兩篇
https://stackoverflow.com/questions/32274259/using-func-property-selector-with-entity-framework-6
https://stackoverflow.com/questions/278684/how-do-i-create-an-expression-tree-to-represent-string-containsterm-in-c
我們在產生 it.a.Contains(value)
時
需要將 Expression.Constant(value)
內的 value 包裝成物件
EF 才會將其轉換成 SQL 參數
//將 value 包裝成物件
var closure = new { value = value };
//產生 { value = 12 }.value
var valueProperty = Expression.Property(
Expression.Constant(closure),
nameof(closure.value));
public static IQueryable<T> WhereContains<T>(this IQueryable<T> source,
string column, string value)
{
//取得 T.column 屬性
var property = typeof(T).GetProperty(column);
//產生 it.a 的 it
var itParameter = Expression.Parameter(typeof(T), "it");
//產生 it.a
Expression expressionProperty = Expression.Property(
itParameter, property.Name);
//產生 string.Contains 函數
var containsMethod = typeof(string)
.GetMethod("Contains", new[] { typeof(string) });
//將 value 包裝成物件
var closure = new { value = value };
//產生 { value = 12 }.value
var valueProperty = Expression.Property(
Expression.Constant(closure),
nameof(closure.value));
//產生 it.a.Contains({ value = 12 }.value)
var selector = Expression.Call(
expressionProperty,
containsMethod,
valueProperty);
//產生 .Where(it => it.a.Contains({ value = 12 }.value))
return source.Where(Expression.Lambda<Func<T, bool>>(selector, itParameter));
}
結果就和原生的語法完全一樣了。 ╰( ̄▽ ̄)╭
exec sp_executesql N'SELECT [it].[a], [it].[b]
FROM [Test] AS [it]
WHERE (CHARINDEX(@__value_0, [it].[a]) > 0) OR (@__value_0 = N'''')',N'@__value_0 nvarchar(4000)',@__value_0=N'12'
https://tyrrrz.me/blog/expression-trees
https://stackoverflow.com/questions/58320767/linq-where-ef-functions-like-why-direct-properties-work-and-reflection-does-no
https://stackoverflow.com/questions/30725363/how-to-call-a-method-with-instance-using-expression
非常感謝大大精闢完整的解答
其中甚至每條code都有相應註解真是簡顯易懂
我找到一個更簡單的方法
可以使用 EF.Property
取得欄位
原來 EF 有提供這種功能,不用自己硬寫
var column = "a";
var value = "12";
var test = await _db.Tests
.Where(it =>
EF.Property<string>(it, column).Contains(value))
.Select(it => new
{
it.a,
it.b
})
.ToListAsync();
exec sp_executesql N'SELECT [it].[a], [it].[b]
FROM [Test] AS [it]
WHERE (CHARINDEX(@__value_0, [it].[a]) > 0) OR (@__value_0 = N'''')',N'@__value_0 nvarchar(4000)',@__value_0=N'12'
WOW~
請問EF有需要另外using什麼才能使用嗎?
發現我的vs沒有內建的修正
另外好奇一下您每次代碼下面的SQL語法是怎麼產生的呢?
我有用到這幾個套件
可能版本不一樣功能會有些差異
SQL 語法是用 SQL Server Profiler 攔截的
EF.Property 是 Microsoft.EntityFrameworkCore 內的函數
public void test(string a = "id", string search = "4")
{
R r1 = new R
{
id = "1234",
projectname = "321",
type = "123456",
name = "456"
};
R r2 = new R
{
id = "456",
projectname = "456789",
type = "987",
name = "888"
};
R[] r={r1,r2};
if (search != "")
{
r = r.Where(x=>(x.GetType().GetProperty(a).GetValue(x).ToString().Contains(search))).ToArray();
}
}
//你自訂select類別
public class R
{
public string id { get; set; }
public string projectname { get; set; }
public string type { get; set; }
public string name { get; set; }
}
這樣子就可以了
可以去讀下Type 類別相關資料
大大您好
預期目標是將傳入的a值作為x.後那個值的變動依據
例如a將傳入"id","projectname","type","name"
這種感覺
喔喔 這我需要想一下了
不好意思讓您誤會了
a500197這樣子就可以了
System.NotSupportedException: 'LINQ to Entities 無法辨識方法 'System.Object GetPropertyValue(System.Object, System.String)' 方法,而且這個方法無法轉譯成存放區運算式。'
出現了這個錯誤代碼
我研究一下哪邊出錯了
感謝大大幫助
a500197你試試自訂你的資料類別記得要{ get; set; }試試看
也可以試試看 GetFields()