'ModelBuilder' does not contain a definition for 'HasDbFunction' - c#

I am trying to override Contains to be case-insensitive in Postgres EF Core 6 like this
modelBuilder.HasDbFunction(typeof(DbFunctionsExtensions), "Contains", (string left, string right) => EF.Functions.Like(left, $"%{right}%")).HasTranslation(args => {
var left = args[0];
var right = args[1];
return $"contains_ci({left}, {right})";
});
Once I have created the migration file, I will add the following code
migrationBuilder.Sql("CREATE OR REPLACE FUNCTION public.contains_ci(text, text) RETURNS boolean AS $$ SELECT $1 ILIKE '%' || $2 || '%' $$ LANGUAGE sql;");
Unfortunately, I got this error:
Error CS1929 'ModelBuilder' does not contain a definition for
'HasDbFunction' and the best extension method overload
'RelationalModelBuilderExtensions.HasDbFunction(IConventionModelBuilder,
string, Type, bool)' requires a receiver of type
'Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionModelBuilder' PostgresEFCoreCaseInsensitive.Module
What can I do to solve this problem?

Related

Why can't I use my enum type inside a switch expression?

I've got an enum called Category, but with a few special attributes for translating the Enum into another langauge.
public enum Category
{
[StringValue("Varm mat")]
HotFood,
[StringValue("Kald mat")]
ColdFood,
}
I'm trying to map from the string value to the enum using a switch expression,
but I get a compilation error: The type name 'HotFood' does not exist on the type 'Category' (and similar for ColdFood).
The GetStringValue() method is an extension method we wrote for getting the StringValue attribute of the Enum.
public static Category MapStringToCategory(string stringValue)
{
var thisOneWorks = Category.HotFood.GetStringValue(); // this debug line works fine
return stringValue switch
{
Category.HotFood.GetStringValue() => Category.HotFood, // error: 'HotFood' does not exist on the type 'Category'
Category.ColdFood.GetStringValue() => Category.ColdFood, // error: 'ColdFood' does not exist on the type 'Category'
_ => throw new InvalidOperationException($"No string value for {stringValue}")
};
}
I can't wrap my head around why Category.HotFood works fine outside of switch expressions,
but not inside them.
I also tried with a regular switch statement, but same error.
Would appreciate some input.
I'm using C# 8 and .NET Core 3.1.
As stated in my comment - Case value should be known at compile time.

TrimStart in Expression Tree with LINQ to Entities

I'm attempting to write an expression tree which can allow dynamic use of a StartsWith() method on non-string valued columns using Entity Framework.
E.g. IntegerValuedColumn.StartsWith(5) would return 500, 5000, 555, 5123, etc
I am trying to write an expression tree based on this answer:
How do I query an integer column for "starts with" in Entity Framework?
Here is what I have so far:
MethodInfo stringConvert = typeof(SqlFunctions).GetMethod("StringConvert", new[] { typeof(double?) });
Expression castExpression = Expression.Convert(propertyExpression, typeof(double?));
Expression convertExpression = Expression.Call(null, stringConvert, castExpression);
MethodInfo trimStart = typeof(string).GetMethod("TrimStart");
Expression nullExpression = Expression.Constant(null, typeof(char[]));
Expression trimExpression = Expression.Call(convertExpression, trimStart, nullExpression);
MethodInfo startsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
Expression methodExpression = Expression.Call(trimExpression, startsWith, constantExpression);
return methodExpression;
When I compile and run this expression, I get the following exception:
The method 'System.String TrimStart(Char[])' is only supported in LINQ to Entities when there are no trim characters specified as arguments.
In the original example, the expression is:
SqlFunctions.StringConvert((double)x.AccountNumber)
.TrimStart().StartsWith(searchTerm)
But what I get comes out as:
StringConvert(Convert(x.AccountNumber)).TrimStart(null).StartsWith(searchTerm)
I have removed the two lines dealing with TrimStart (nullExpression and trimExpression) and verified that the statement runs (excluding the thought that the error is being caused by the different language use). My theory based on the exception message is that the TrimStart() method wants to be called with zero parameters, but when I try that the Expression builder tells me that the incorrect number of parameters have been passed in so I figure I'm just missing something.
How would I go about calling the TrimStart method like TrimStart() instead of TrimStart(null) or TrimStart(new char[0]) using expression trees?

Unable to create a constant value of type 'System.Object' in Entity Framework

I have a table named UserTenders having many-to-one relationship with aspnet_Membership table.
I am using EntityFramework 4.0 and when I try something like this, it errors.
var tenders = ctx.UserTenders
.Where(tender => tender.HasAdminApproved.Equals(true))
.ToList();
The error is
System.NotSupportedException
Unable to create a constant value of type 'System.Object'.
Only primitive types ('such as Int32, String, and Guid') are supported in this context.
This snippet below works.
var tenders = ctx.UserTenders.ToList();
What could be wrong in my code? Feel like I am missing something very trivial.
I would like to filter all those rows that have the bit field HasAdminApproved as true
Try replacing
.Where(tender => tender.HasAdminApproved.Equals(true))
With:
.Where(tender => tender.HasAdminApproved == true)
Or as previously suggested by #Ladislav Mrnka if your field is bool?
.Where(tender => tender.HasAdminApproved)
#Ladislav told you the correct answer (.Where(tender => tender.HasAdminApproved)), but you might wonder why you get this message.
You're trying to call System.Boolean.Equals(Object obj). So you're boxing the constant true. And L2E, as the message says, has no support for a const of a non-primitive type like System.Object. Hence the error.
I had the same exception caused by a different problem: a char versus a string used as a constant. My select looked like this:
from p in Person
select new Foo
{
FullName = p.FirstName + ' ' + p.LastName
}
The boolean conditionals I had elsewhere in the query (i.e. "where p.IsActive") worked fine. I had to switch to using a string:
from p in Person
select new Foo
{
FullName = p.FirstName + " " + p.LastName
}
This obviously is not the answer to the OP's question, but I was unable to find a similar question with the char/string issue so I wanted to post it for others' benefit.
Just wanted to point out, you could have also used tender.HasAdminApproved.HasValue.Equals(true))... this works when bool allows nulls

Building a common set of methods that can operate on any linq table

Problem: We make extensive use of a repository pattern to facilitate read/write operations on our datastore (MS SQL using LINQ) across multiple applications and subsections of functionality. We have series of methods that all do something similar to each other.
For example, we have the ProcessAndSortXXXXX class of methods.
private static IEnumerable<ClassErrorEntry> ProcessAndSortClassErrorLog(IQueryable<ClassErrorDb> queryable, string sortOrder)
{
var dynamic = queryable;
if (!String.IsNullOrEmpty(sortOrder.Trim()))
{
dynamic = dynamic.OrderBy(sortOrder);
}
return dynamic
.Select(l =>
new ClassErrorEntry(l.Id)
{
ClassId = l.ClassId,
Code = l.Code,
Message = l.Message,
Severity = l.Severity,
Target = l.Target
}
);
}
...and...
private static IEnumerable<ClassTimerLogEntry> ProcessAndSortClassTimerLog(IQueryable<ClassTimerDb> queryable, string sortOrder)
{
var dynamic = queryable;
if (!String.IsNullOrEmpty(sortOrder.Trim()))
{
dynamic = dynamic.OrderBy(sortOrder);
}
return dynamic
.Select(l =>
new ClassTimerLogEntry(l.Id)
{
ClassName = l.ClassName,
MethodName = l.MethodName,
StartTime = l.StartTime,
EndTime = l.EndTime,
ParentId = l.ParentId,
ExecutionOrder = l.ExecutionOrder
}
);
}
As you can tell by the code, they're all very similar until you look at the signature and then get to the the return statement where we're building out the instances of the ClassErrorEntry and ClassTimerLogEntry.
I want to build a utility method that I'll add into the base class that all of the repositories inherit from.
I want to be able to pass in arguments that can be used to instantiate the objects and pack them into the returning IEnumerable.
I found this post by ScottGu and that gets me most of what I need. It looks like this (from the sample in the documentation):
var query =
db.Customers.
Where("City = #0 and Orders.Count >= #1", "London", 10).
OrderBy("CompanyName").
Select("new(CompanyName as Name, Phone)");
Here's where I get stuck, though. I need a pointer or suggestion how I can pass in the LINQ tables and DataContext in a generic fashion so I can build out the dynamic query.
If I were to mock up the signature in pseudocode I think it would look something like this:
protected internal IEnumerable ProcessAndSort(IQueryable source, string selectClause, string whereClause, string orderByClause);
I realize that the finished signature may look different as we figure this out.
Thank you!
Update!
I now have code that works to generate an anonymous type but fails when converting to the concrete type.
public static IEnumerable<TResult> ProcessAndSort<T, TResult>(IQueryable<T> queryable,
string selector, Expression<Func<T, bool>> predicate, string sortOrder)
{
var dynamic = queryable.Where(predicate).AsQueryable();
if (!String.IsNullOrEmpty(sortOrder.Trim()))
{
dynamic = dynamic.OrderBy(sortOrder);
}
var result= dynamic.Select(selector).Cast<TResult>();
return result;
}
Here is the code that calls this method:
[TestMethod]
public void TestAnonymousClass()
{
var loggingContext = new LoggingDbDataContext(DatabaseConnectionString);
var repo = new LoggingRepository(loggingContext);
var result = repo.TestGetClassErrorLog(4407, 10, 0,
"new ( ClassId as ClassId, " +
"Code as Code, " +
"Message as Message, " +
"Severity as Severity, " +
"Target as Target )", "Target");
TestContext.WriteLine(result.ToList().Count.ToString());
}
The last line TestContext.WriteLine(result.ToList().Count.ToString()); throws the exception System.InvalidOperationException: No coercion operator is defined between types 'DynamicClass1' and 'Utilities.Logging.ClassErrorEntry'.
This chunk of code, though fails:
[TestMethod]
public void TestNamedClass()
{
var loggingContext = new LoggingDbDataContext(DatabaseConnectionString);
var repo = new LoggingRepository(loggingContext);
var result = repo.TestGetClassErrorLog(4407, 10, 0,
"new ClassErrorEntry(Id) { ClassId = ClassId, " +
"Code = Code, " +
"Message = Message, " +
"Severity = Severity, " +
"Target = Target }", "Target");
TestContext.WriteLine(result.ToList().Count.ToString());
}
This fails on a parsing error. Test method eModal.Repositories.Test.RepositoryBaseTest.TestConcreteClass threw exception:
System.Linq.Dynamic.ParseException: '(' expected, found 'ClassErrorEntry' ('Identifier') at char 19 in 'new ClassErrorEntry(Id) { ChassisAuthId = ChassisAuthId, Code = Code, Message = Message, Severity = Severity, Target = Target }'
I'm not sure that the character position is suspectas the 19th character position is a ( and the type passed into the Validate method indicates a position of 4, or the first 'C'.
I would completely advise you against making weakly typed queries just for the sake of code reuse.
Code reuse is for increasing maintainability, but weak typing can kill it, if used in a wrong way.
By writing your queries in plain text, you're effectively making the classes very hard to refactor and change, and introduce a lot of obscure dependencies.
I suggest you take a look at LinqKit that allows to combine Expressions. For example, we wrote a Paging method that splits query by pages and use it across the project with different types:
var query = CompiledQuery.Compile(
BuildFolderExpr( folder, false )
.Select( msg => selector.Invoke( msg, userId ) ) // re-use selector expression
.OrderBy( mv => mv.DateCreated, SortDirection.Descending )
.Paging() // re-use paging expression
.Expand() // LinqKit method that "injects" referenced expressions
)
public static Expression<Func<T1, T2, PagingParam, IQueryable<TItem>>> Paging<T1, T2, TItem>(
this Expression<Func<T1, T2, IQueryable<TItem>>> expr )
{
return ( T1 v1, T2 v2, PagingParam p ) => expr.Invoke( v1, v2 ).Skip( p.From ).Take( p.Count );
}
In my example, BuildMessageExpr returns a relatively simple select expression (which already depends on folder and another parameter), and different methods reuse this expression by applying filtering, ordering, getting count, further selecting with selector expression being passed as a parameter, et cetera. Once the query is created, it gets cached for future usage when parameters are similar.
It is not a direct answer to your question.
As you have said you have quite a lot code that look similar but return different types. If you will go ahead and look for generic implementation of this approach, the result may have a few hacks, you may still pass some uncomfortable SQL or check the type of the object or do some reflection kung-fu. You may still select this pass and actually someone can have a sensible idea that wouldn't look like a dirty hack.
The other option is to use a proper ORM with generic repository pattern and dependency injection(google link). Your data access layer will look much simpler and easier to maintain.

extending SqlMethods.Like to support property name

I'm trying to extend SqlMethods.Like method to support property name rather than property value, i wrote the following extension method :
public static bool Like(this object obj, string propertyName, string pattern)
{
var properties = obj.GetType().GetProperties().Select(p => p.Name);
if(!properties.Contains(propertyName))
throw new Exception(string.Format("Object does not contain property:{0}", propertyName));
return SqlMethods.Like(obj.GetType().GetProperty(propertyName).GetValue(obj, null).ToString(), pattern);
}
however the method throws the following exception :
Method 'Boolean Like(System.Object, System.String, System.String)' has no supported translation to SQL.
how can i write an extension method with transaction to SQL support ?
I found this answer from RichardD that is exactly the correct answer. Reposting for clarity, but original is linked below.
using System;
using System.Linq;
using System.Linq.Expressions;
public static class Extensions
{
public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, string propertyName, string pattern)
{
if (null == source) throw new ArgumentNullException("source");
if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName");
var a = Expression.Parameter(typeof(T), "a");
var prop = Expression.Property(a, propertyName);
var body = Expression.Call(typeof(SqlMethods), "Like", null, prop, Expression.Constant(pattern));
var fn = Expression.Lambda<Func<T, bool>>(body, a);
return source.Where(fn);
}
}
...
.WhereLike("Description", "%a%b%c%"));
The solution uses expression trees, but all advanced LinqToSql operations will require familiarity with that.
From: http://forums.asp.net/p/1488418/3503874.aspx
What you want to do does not seem to make sense in the contxt of what SqlMethods.Like actually does. When you pass in a property of a class you are essentially telling it to translate that into the equivelent field in the SQL query. e.g.
var result = from names in db.Names
where SqlMethods.Like(names.FullName, '%Smith%')
select names;
would translate to something like:
SELECT *
FROM Names
WHERE Fullname LIKE '%Smith%'
(in practice it would be different using parameters and sp_executeSQL but coneptually that is what it would do).
If you want to pass in the name of a property what does that mean in terms of SQL, conceptually it makes no sense e.g.
SELECT *
FROM Names
WHERE --what would go here-- LIKE '%Smith%'
As such you are not going to be able to create a Linq To SQL method that creates nonsense SQL.
What are you actually trying to do, the chance is that you are going about it completely the wrong way.
Edit:hmm from your comment i think i understand what you want to do, in essense you want to be able to specify the column you are doing a LIKE comparison with at run time. You cannot do it exactly. You could use a stored procedure that used dynamic SQL and took a string parameter for the column. You could then expose this as a method on your data context class.

Categories