diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a01a2b0..1b69f24 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - dotnet-version: ['6.0.x'] + dotnet-version: ['8.0.x'] steps: - uses: actions/checkout@v4 @@ -26,7 +26,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: '6.0.x' + dotnet-version: '8.0.x' - name: Build package run: dotnet build --configuration 'Release' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ad1133e..2b5712c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: '6.0.x' + dotnet-version: '8.0.x' - name: Build packages run: dotnet build --configuration 'Release' diff --git a/DataTables.NetStandard.Sample/DataTables.NetStandard.Sample.csproj b/DataTables.NetStandard.Sample/DataTables.NetStandard.Sample.csproj index 39cc9b3..8d4176c 100644 --- a/DataTables.NetStandard.Sample/DataTables.NetStandard.Sample.csproj +++ b/DataTables.NetStandard.Sample/DataTables.NetStandard.Sample.csproj @@ -1,16 +1,19 @@ - + - net6.0 + net8.0 MIT + 3635bc4d-44f6-4d28-930e-d7d3b2ea765e - - + - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/DataTables.NetStandard.Sample/DataTables/PersonDataTable.cs b/DataTables.NetStandard.Sample/DataTables/PersonDataTable.cs index ba22e6e..833e290 100644 --- a/DataTables.NetStandard.Sample/DataTables/PersonDataTable.cs +++ b/DataTables.NetStandard.Sample/DataTables/PersonDataTable.cs @@ -28,9 +28,9 @@ public override IList> Columns() PrivatePropertyName = nameof(Person.Id), IsOrderable = true, IsSearchable = true, - SearchPredicate = (p, s) => false, // The fallback predicate will never match, but since we declared a provider, it is not used anyway. - SearchPredicateProvider = (s) => (p, s) => true, // The provider will return a predicate matching all entities (used for global search). This is just for illustration, it makes no sense. - ColumnSearchPredicateProvider = (s) => // The column provider will return a predicate matching entities in a numeric range if the search term is properly formatted. + SearchPredicate = (p, s) => false, // The fallback predicate will never match, but since we declared a provider, it is not used anyway. + //SearchPredicateProvider = (s) => (p, s) => true, // The provider will return a predicate matching all entities (used for global search). This is just for illustration, it makes no sense. + ColumnSearchPredicateProvider = (s) => // The column provider will return a predicate matching entities in a numeric range if the search term is properly formatted. { var minMax = s.Split("-delim-", System.StringSplitOptions.RemoveEmptyEntries); if (minMax.Length >= 2) diff --git a/DataTables.NetStandard.Sample/Properties/launchSettings.json b/DataTables.NetStandard.Sample/Properties/launchSettings.json index 782c98d..156b1cc 100644 --- a/DataTables.NetStandard.Sample/Properties/launchSettings.json +++ b/DataTables.NetStandard.Sample/Properties/launchSettings.json @@ -1,12 +1,4 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53215", - "sslPort": 44331 - } - }, +{ "profiles": { "IIS Express": { "commandName": "IISExpress", @@ -18,10 +10,28 @@ "DataTables.NetStandard.Sample": { "commandName": "Project", "launchBrowser": true, - "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - } + }, + "applicationUrl": "https://localhost:5003;http://localhost:5002" + }, + "WSL": { + "commandName": "WSL2", + "launchBrowser": true, + "launchUrl": "https://localhost:5001", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "https://localhost:5003;http://localhost:5002" + }, + "distributionName": "" + } + }, + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53215", + "sslPort": 44331 } } } \ No newline at end of file diff --git a/DataTables.NetStandard/DataTables.NetStandard.csproj b/DataTables.NetStandard/DataTables.NetStandard.csproj index caa0a10..8e1d8f6 100644 --- a/DataTables.NetStandard/DataTables.NetStandard.csproj +++ b/DataTables.NetStandard/DataTables.NetStandard.csproj @@ -4,9 +4,9 @@ netstandard2.1 latest true - 3.0.1 - 3.0.1.0 - 3.0.1.0 + 3.0.2 + 3.0.2.0 + 3.0.2.0 Namoshek (Marvin Mall) Namoshek (Marvin Mall) DataTables.NetStandard diff --git a/DataTables.NetStandard/DataTablesRequest.cs b/DataTables.NetStandard/DataTablesRequest.cs index c1744cf..30f09a3 100644 --- a/DataTables.NetStandard/DataTablesRequest.cs +++ b/DataTables.NetStandard/DataTablesRequest.cs @@ -51,13 +51,13 @@ public class DataTablesRequest /// /// Collection of DataTables column info. - /// Each column can be acessed via indexer by corresponding property name or by property selector. + /// Each column can be acessed via indexer by corresponding property name or by property selector. /// /// /// Example for an entity Student that has public property FirstName. /// /// // Get DataTables request from Http query parameters - /// var request = new DataTablesRequest<Student>(url); + /// var request = new DataTablesRequest<Student>(url); /// /// // Access by property name /// var column = request.Columns["FirstName"]; @@ -70,7 +70,7 @@ public class DataTablesRequest new List>(); /// - /// Set this property to log incoming request parameters and resulting queries to the given delegate. + /// Set this property to log incoming request parameters and resulting queries to the given delegate. /// For example, to log to the console, set this property to . /// public Action Log { get; set; } @@ -194,7 +194,7 @@ protected void ParseGlobalConfigurationFromQuery(NameValueCollection query) { int start = int.TryParse(query["start"], out start) ? start : 0; PageSize = int.TryParse(query["length"], out int length) ? length : 15; - PageNumber = start / PageSize + 1; + PageNumber = (start / PageSize) + 1; Draw = int.TryParse(query["draw"], out int draw) ? draw : 0; diff --git a/DataTables.NetStandard/Extensions/QueryableExtensions.cs b/DataTables.NetStandard/Extensions/QueryableExtensions.cs index 8fd2a0a..10e620c 100644 --- a/DataTables.NetStandard/Extensions/QueryableExtensions.cs +++ b/DataTables.NetStandard/Extensions/QueryableExtensions.cs @@ -200,7 +200,7 @@ internal static IQueryable ApplyGlobalSearchFilter(); - var searchValueConstant = Expression.Constant(globalSearchValue, typeof(string)); + var searchValueConstant = ExpressionHelper.CreateConstantFilterExpression(globalSearchValue, typeof(string)); expression = (Expression>)Expression.Lambda( Expression.Invoke(expr, entityParam, searchValueConstant), entityParam); @@ -233,7 +233,7 @@ internal static IQueryable ApplyGlobalSearchFilter - /// Applies the search filter for each of the searchable + /// Applies the search filter for each of the searchable /// where a search value is present. /// /// The type of the entity. @@ -272,7 +272,7 @@ internal static IQueryable ApplyColumnSearchFilter(); - var searchValueConstant = Expression.Constant(c.SearchValue, typeof(string)); + var searchValueConstant = ExpressionHelper.CreateConstantFilterExpression(c.SearchValue, typeof(string)); expression = (Expression>)Expression.Lambda( Expression.Invoke(expr, entityParam, searchValueConstant), entityParam); diff --git a/DataTables.NetStandard/Util/ExpressionHelper.cs b/DataTables.NetStandard/Util/ExpressionHelper.cs index 5e9cd52..2b0c7e0 100644 --- a/DataTables.NetStandard/Util/ExpressionHelper.cs +++ b/DataTables.NetStandard/Util/ExpressionHelper.cs @@ -38,7 +38,7 @@ internal static ParameterExpression BuildParameterExpression() /// /// Builds an for the given . - /// The property name has to be given as dot-separated property path, e.g. Destination.Location.City. + /// The property name has to be given as dot-separated property path, e.g. Destination.Location.City. /// /// The parameter. /// Name of the property. @@ -85,7 +85,7 @@ internal static Expression> BuildStringContainsPredicate> BuildRegexPredicate(str exp = Expression.Call(propertyExp, Object_ToString); } - var regexExp = Expression.Constant(regex, typeof(string)); + var regexExp = CreateConstantFilterExpression(regex, typeof(string)); var resultExp = Expression.Call(Regex_IsMatch, exp, regexExp); var notNullExp = Expression.NotEqual(exp, Expression.Constant(null, typeof(object))); return Expression.Lambda>(Expression.AndAlso(notNullExp, resultExp), parameterExp); } + + /// + /// Creates a constant filter expression of the given and converts the type to the given . + /// + /// + /// + internal static Expression CreateConstantFilterExpression(object value, Type type) + { + // The value is converted to anonymous function only returning the value itself. + Expression> valueExpression = () => value; + + // Afterwards only the body of the function, which is the value, is converted to the delivered type. + // Therefore no Expression.Constant is necessary which lead to memory leaks, because EFCore caches such constants. + // Caching constants is not wrong, but creating constants of dynamic search values is wrong. + return Expression.Convert(valueExpression.Body, type); + } } }