簡體   English   中英

從linq到實體查詢動態構建選擇列表

[英]Dynamically build select list from linq to entities query

我正在尋找一種從iQueryable對象動態創建選擇列表的方法。

具體的例子,我想做類似以下的事情:

public void CreateSelectList(IQueryable(of EntityModel.Core.User entities), string[] columns)
{
    foreach(var columnID in columns)
    {
        switch(columnID)
        {
            case "Type":
                SelectList.add(e => e.UserType);
                break;
            case "Name":
                SelectList.add(e => e.Name);
                break;
            etc....
        }
    }
    var selectResult = (from u in entities select objSelectList);
}

因此,所有屬性都是已知的,但我事先並不知道要選擇哪些屬性。 這將通過columns參數傳遞。

我知道我將遇到selectResult類型的問題,因為當選擇列表是動態的時,編譯器不知道匿名類型的屬性需要是什么。

如果上述情況不可能:我需要的方案如下:
我正在嘗試創建一個可以實現的類來顯示分頁/過濾的數據列表。 這些數據可以是任何東西(取決於實現)。使用的linq是實體的linq。 所以它們直接鏈接到sql數據。 現在我只想選擇我實際在列表中顯示的實體的列。 因此我希望select是動態的。 我的實體可能有一百個屬性,但如果列表中只顯示了3個屬性,我不想生成一個選擇所有100列數據的查詢,然后只使用其中的3個。 如果有一種我沒有想到的不同方法,我會接受各種想法

編輯:

關於約束的一些澄清:
- 查詢需要與linq一起使用實體(參見問題主題)
- 一個實體可能包含100列,因此選擇所有列然后只讀取我需要的列不是一個選項。
- 最終用戶決定顯示哪些列,因此要在運行時確定要選擇的列
- 我需要創建一個SINGLE選擇,有多個select語句意味着在數據庫上有多個查詢,這是我不想要的

動態選擇表達式到編譯時已知類型可以使用Expression.MemberInit方法輕松構建,其中MemberBinding是使用Expression.Bind方法創建的。

這是一個自定義擴展方法,它執行以下操作:

public static class QueryableExtensions
{
    public static IQueryable<TResult> Select<TResult>(this IQueryable source, string[] columns)
    {
        var sourceType = source.ElementType;
        var resultType = typeof(TResult);
        var parameter = Expression.Parameter(sourceType, "e");
        var bindings = columns.Select(column => Expression.Bind(
            resultType.GetProperty(column), Expression.PropertyOrField(parameter, column)));
        var body = Expression.MemberInit(Expression.New(resultType), bindings);
        var selector = Expression.Lambda(body, parameter);
        return source.Provider.CreateQuery<TResult>(
            Expression.Call(typeof(Queryable), "Select", new Type[] { sourceType, resultType },
                source.Expression, Expression.Quote(selector)));
    }
}

唯一的問題是什么是TResult類型。 EF Core中,您可以傳遞實體類型(如EntityModel.Core.User中的EntityModel.Core.User ),它將起作用。 EF 6及更早版本中,您需要一個單獨的非實體類型,否則您將獲得NotSupportedException - 無法在LINQ to Entities查詢中構造實體或復雜類型

更新:如果你想要刪除字符串列,我建議你用以下類替換擴展方法:

public class SelectList<TSource>
{
    private List<MemberInfo> members = new List<MemberInfo>();
    public SelectList<TSource> Add<TValue>(Expression<Func<TSource, TValue>> selector)
    {
        var member = ((MemberExpression)selector.Body).Member;
        members.Add(member);
        return this;
    }
    public IQueryable<TResult> Select<TResult>(IQueryable<TSource> source)
    {
        var sourceType = typeof(TSource);
        var resultType = typeof(TResult);
        var parameter = Expression.Parameter(sourceType, "e");
        var bindings = members.Select(member => Expression.Bind(
            resultType.GetProperty(member.Name), Expression.MakeMemberAccess(parameter, member)));
        var body = Expression.MemberInit(Expression.New(resultType), bindings);
        var selector = Expression.Lambda<Func<TSource, TResult>>(body, parameter);
        return source.Select(selector);
    }
}

樣本用法:

var selectList = new SelectList<EntityModel.Core.User>();
selectList.Add(e => e.UserType);
selectList.Add(e => e.Name);

var selectResult = selectList.Select<UserDto>(entities);

你想要的是可能的,但這並不簡單。 您可以使用System.Linq.Expressions命名空間中的方法和類動態構建EF查詢。

有關如何動態構建Select表達式的一個很好的示例,請參閱此問題

我相信這就是你所需要的:

var entities = new List<User>();

entities.Add(new User { Name = "First", Type = "TypeA" });
entities.Add(new User { Name = "Second", Type = "TypeB" });

string[] columns = { "Name", "Type" };

var selectResult = new List<string>();

foreach (var columnID in columns)
{
    selectResult.AddRange(entities.Select(e => e.GetType().GetProperty(columnID).GetValue(e, null).ToString()));
}

foreach (var result in selectResult)
{
    Console.WriteLine(result);
}

此代碼輸出:

  • 第一
  • 第二
  • 類型A
  • 的TypeB

更新(根據評論)

// initialize alist of entities (User)
var entities = new List<User>();
entities.Add(new User { Name = "First", Type = "TypeA", SomeOtherField="abc" });
entities.Add(new User { Name = "Second", Type = "TypeB", SomeOtherField = "xyz" });

// set the wanted fields
string[] columns = { "Name", "Type" };

// create a set of properties of the User class by the set of wanted fields
var properties = typeof(User).GetProperties()
                        .Where(p => columns.Contains(p.Name))
                        .ToList();

// Get it with a single select (by use of the Dynamic object)
var selectResult = entities.Select(e =>
{
    dynamic x = new ExpandoObject();
    var temp = x as IDictionary<string, Object>;
    foreach (var property in properties)
        temp.Add(property.Name, property.GetValue(e));
    return x;
});

// itterate the results
foreach (var result in selectResult)
{
    Console.WriteLine(result.Name);
    Console.WriteLine(result.Type);
}

此代碼輸出:

  • 第一
  • 類型A
  • 第二
  • 的TypeB

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM