I have this sample query:
context.BarcodeTipiDoc.AsQueryable().Where(d => d.Barcode.CompareTo(minBarcode) > 0);
You can define the column as non-unicode in the context's OnModelCreating
override:
modelBuilder.Entity<BarcodeTipiDoc>().Property(x => x.Barcode).IsUnicode(false);
There is a way to tell to Entity Framework the right type of the sqlparameter?
Currently (EF Core 2.x, 3.0) there is no such way. EF Core tries to infer the parameter type from the usage inside expressions.
So TypeName = "varchar(21)
or .IsUnicode(false).HasMaxLength(21)
column mapping is step in the right direction.
Unfortunately 2.x parameter type inference succeeds for comparison operators like ==
, >
etc. but fails for methods like string.CompareTo
, string.Compare
etc.
This has been fixed in 3.0, but now the translation is far from optimal (CASE WHEN ... > 0
rather than simply >
) and also there are many breaking changes, so upgrading just because of that doesn't make sense and is risky.
What I can offer is a solution based on a custom mapped database scalar methods similar to Entity Framework Core: Guid Greater Than for Paging. It introduces several string
custom methods which are mapped to string
comparison operators:
public static class StringFunctions
{
public static bool IsGreaterThan(this string left, string right) => string.Compare(left, right) > 0;
public static bool IsGreaterThanOrEqual(this string left, string right) => string.Compare(left, right) >= 0;
public static bool IsLessThan(this string left, string right) => string.Compare(left, right) < 0;
public static bool IsLessThanOrEqual(this string left, string right) => string.Compare(left, right) <= 0;
public static ModelBuilder RegisterStringFunctions(this ModelBuilder modelBuilder) => modelBuilder
.RegisterFunction(nameof(IsGreaterThan), ExpressionType.GreaterThan)
.RegisterFunction(nameof(IsGreaterThanOrEqual), ExpressionType.GreaterThanOrEqual)
.RegisterFunction(nameof(IsLessThan), ExpressionType.LessThan)
.RegisterFunction(nameof(IsLessThanOrEqual), ExpressionType.LessThanOrEqual);
static ModelBuilder RegisterFunction(this ModelBuilder modelBuilder, string name, ExpressionType type)
{
var method = typeof(StringFunctions).GetMethod(name, new[] { typeof(string), typeof(string) });
modelBuilder.HasDbFunction(method).HasTranslation(parameters =>
{
var left = parameters.ElementAt(0);
var right = parameters.ElementAt(1);
// EF Core 2.x
return Expression.MakeBinary(type, left, right, false, method);
});
return modelBuilder;
}
}
For EF Core 3.0 replace
return Expression.MakeBinary(type, left, right, false, method);
with (plus respective using
s)
if (right is SqlParameterExpression rightParam)
right = rightParam.ApplyTypeMapping(left.TypeMapping);
else if (left is SqlParameterExpression leftParam)
left = leftParam.ApplyTypeMapping(right.TypeMapping);
return new SqlBinaryExpression(type, left, right, typeof(bool), null);
Now all you need is to call
modelBuilder.RegisterStringFunctions();
inside your OnModelCreating
override.
Then inside your query, instead of
d => d.Barcode.CompareTo(minBarcode) > 0
use
d => d.Barcode.IsGreaterThan(minBarcode)
and it will be translated to
[d].[Barcode] > @__minBarcode_0
with correct db parameter type (same as the db type of BarCode
column).
In your column mapping, you originally declared this:
[Column("Barcode", TypeName = "varchar(21)")]
public string Barcode { get; set; }
Can you try this:
[Column(TypeName = "VARCHAR(21)")]
public string Barcode { get; set; }
Or you can specify in the Model Builder:
modelBuilder.Entity<BarCodeTipiDoc>()
.Property(x=> x.BarCode)
.HasColumnType("varchar(21)");
It would also help if you could post the model for your object BarcodeTipiDoc
.
Update: Just saw that you were using EF Core.