簡體   English   中英

嘗試使用 LINQ 以數據庫字段作為參數獲取不同的值

[英]Trying to get distinct values using LINQ with database field as parameter

我正在嘗試使用 Linq 查詢從 MsSQL 數據庫中選擇不同的值並將數據庫字段作為標准傳遞,但它給了我錯誤。

如果一個表有以下數據:

Name | Age | Class | school
----------------------------
Anna,     23, grade 2, Havard
Kendricks,34, grade 2, Havard
Vikander, 27, grade 3, Covenant
Hathaway, 18, grade1,  Covenant
Gemma,    23, grade 4, Bowen
Jolie,    23, grade 5, Havard
Arteton,  24, grade 1, Bayero
Ana Armas 30, grade 2, Coventry

現在,從上表中,我試圖通過傳遞“學校”或“班級”甚至更多字段來檢索數據,然后根據這些字段返回不同的值。 我該怎么做?

// filterParam - is the field(class, school)
// then how do I select the distinct values...

下面是我的代碼:

  public IEnumerable<ScbDataInfo> GetScbOptionsByFilter(string filterParam) {
            using (SRSContext entityContext = new SRSContext()) {

                var query = (from e in entityContext.Set<ScbDataInfo>()

                             where e[filterParam] == searchParam   //i passed it here
                             orderby e.RefNo, e.datepmt                             
                             select e).Distinct();

                return query.ToArray();

            }
        }

這是一些代碼,我前段時間從Alexander Krutov許可的MIT 的DataTables.Queryable 中拼湊起來:

這通過表達式工作並且不需要在您自己調用ToArray之前具體化您的數據:

/// <summary>
/// Creates predicate expression like 
/// <code>(T t) => t.SomeProperty.Contains("Constant")</code> 
/// where "SomeProperty" name is defined by <paramref name="stringConstant"/> parameter, and "Constant" is the <paramref name="stringConstant"/>.
/// If property has non-string type, it is converted to string with <see cref="object.ToString()"/> method.
/// </summary>
/// <typeparam name="T">Data type</typeparam>
/// <param name="propertyName">Property name</param>
/// <param name="stringConstant">String constant to construnt the <see cref="string.Contains(string)"/> expression.</param>
/// <param name="caseInsensitive">Case insenstive Contains Predicate?</param>
/// <returns>Predicate instance</returns>
public static Expression<Func<T, bool>> BuildStringContainsPredicate<T>(string propertyName, string stringConstant, bool caseInsensitive)
{
    var type = typeof(T);
    var parameterExp = Expression.Parameter(type, "e");
    var propertyExp = BuildPropertyExpression(parameterExp, propertyName);

    Expression exp = propertyExp;

    // if the property value type is not string, it needs to be casted at first
    if (propertyExp.Type != typeof(string))
    {
        // If we have an Enum, the underlying Entity Framework Provider can not translate the Enum to SQL.
        // Therefore we converting it first to the underlying primitive type (byte, int16, int32, int64 etc)
        //Todo: Sideeffects beobachten
        //Todo: Evtl möglichkeit finden Display Attribute zu implementieren um eine String Suche zu ermöglichen?
        //Todo: Notwendigkeit in NET Core 2.1 überprüfen
        if (propertyExp.Type.IsEnum)
        {
            exp = Expression.Convert(exp, Enum.GetUnderlyingType(propertyExp.Type));
        }

        exp = Expression.Call(exp, ObjectToString);
    }

    // call ToLower if case insensitive search
    if (caseInsensitive)
    {
        exp = Expression.Call(exp, StringToLower);
        stringConstant = stringConstant.ToLower();
    }
    var someValue = Expression.Constant(stringConstant, typeof(string));
    var containsMethodExp = Expression.Call(exp, StringContains, someValue);
    return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}

/// <summary>
/// Builds the property expression from the full property name.
/// </summary>
/// <param name="param">Parameter expression, like <code>e =></code></param>
/// <param name="propertyName">Name of the property</param>
/// <returns>MemberExpression instance</returns>
private static MemberExpression BuildPropertyExpression(ParameterExpression param, string propertyName)
{
    var parts = propertyName.Split('.');
    Expression body = param;
    foreach (var member in parts)
    {
        body = Expression.Property(body, member);
    }
    return (MemberExpression)body;
}

/// <summary>
/// <see cref="object.ToString()"/> method info. 
/// Used for building search predicates when the searchable property has non-string type.
/// </summary>
private static readonly MethodInfo ObjectToString = typeof(object).GetMethod(nameof(ToString));


/// <summary>
/// <see cref="string.ToLower()"/> method info. 
/// Used for conversion of string values to lower case.
/// </summary>
private static readonly MethodInfo StringToLower = typeof(string).GetMethod(nameof(string.ToLower), new Type[] { });

/// <summary>
/// <see cref="string.Contains(string)"/> method info. 
/// Used for building default search predicates.
/// </summary>
private static readonly MethodInfo StringContains = typeof(string).GetMethod(nameof(string.Contains), new[] { typeof(string) });

這將創建一個過濾器表達式,其中propertyName是列, stringConstant是搜索值,如果搜索是否區分大小寫,則為 bool。 T是您的IQueryable<T>的類型。

使用PredicateBuilder您可以執行以下操作:

public static IQueryable<T> FilterColumns(this IQueryable<T> query, IEnumerable<string> columns, string searchValue)
{
    Expression<Func<T, bool>> predicate = null;
    foreach (var column in columns)
    {
        var expr = BuildStringContainsPredicate<T>(column,
                       searchValue, false);
        predicate = predicate == null ? PredicateBuilder.Create(expr) : predicate.Or(expr);
    }
    return query.Where(predicate);
}

這提供了一個擴展方法,因此包含類必須是靜態的。

現在您可以執行以下操作:

entityContext.ScbDataInfos
    .FilterColumns(columnNames, searchValue)
    .OrderBy(e => e.RefNo)
    .ThenBy(e => e.datepm)
    .Distinct()
    .ToArray();

在這里,我展示了一個可行的解決方案,對代碼的注釋解釋了它是如何工作的。

    /// <summary>
    /// Filter ScbDataInfo with de field and value indicated
    /// </summary>
    /// <param name="filterParam">Field name</param>
    /// <param name="searchParam">Value used in filter</param>
    /// <returns></returns>
    public IEnumerable<ScbDataInfo> GetScbOptionsByFilter(string filterParam, string searchParam)
    {
        // Here get property using reflection 
        var typeScbDataInfo = typeof(ScbDataInfo);
        var property = typeScbDataInfo.GetProperty(filterParam);

        //var filterExpression =
        using (var context = new SRSContext())
        {
            var query = context.ScbDataInfo
                .ToArray() // It force linq to sql to obtain all records from database. A poor implementation
                .Where(
                    m => property.GetValue(m) // Get entity with reflection
                            .ToString() // Convert to string because searchParam is string. It could be changed for the correct type or using dynamic type
                            .Equals(searchParam) // Simple equals for filter
                );
            return query.ToArray(); // Return array. Poor implementation
        }
    }

如何測試它的示例

    static void Main(string[] args)
    {
        Console.WriteLine("Filter NAME:");
        var filterName = Console.ReadLine();
        Console.WriteLine("Filter VALUE:");
        var filterValue = Console.ReadLine();

        var program = new Program();
        var results = program.GetScbOptionsByFilter(filterName, filterValue);

        Console.WriteLine($"Total results: {results.Count()}");
        Console.ReadKey();
    }

PS:它的實現很差,因為使用了 ToArray() 所以它獲取所有記錄,然后完成 Where 。

我認為使用表達式樹可以實現更好的實現。

無論如何,用普通電腦有幾千條記錄,它的工作正常。

暫無
暫無

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

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