-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add API to configure database types for DbFunction's parameters #13752
Comments
Triage: this is too risky to try to fix for A workaround is below, but it won't work with preview3 due to other bugs to be fixed for RTM. Also, the workaround requires using internal code, which make break in subsequent releases. public static class GeoExtensions
{
public static double Distance(this GeoPoint x, GeoPoint y)
=> throw new NotImplementedException();
}
public struct GeoPoint
{
public GeoPoint(double lat, double lon)
{
Lat = lat;
Lon = lon;
}
public double Lat { get; }
public double Lon { get; }
}
public class House
{
public int Id { get; set; }
public GeoPoint Location { get; set; }
}
public class GeoPointConverter : ValueConverter<GeoPoint, IPoint>
{
public static readonly GeoPointConverter Instance = new GeoPointConverter();
private static readonly IGeometryFactory _geoFactory
= NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
public GeoPointConverter()
: base(
v => _geoFactory.CreatePoint(new Coordinate(v.Lon, v.Lat)),
v => new GeoPoint(v.Y, v.X))
{
}
}
public class BloggingContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ReplaceService<IRelationalTypeMappingSource, ReplacementTypeMappingSource>()
.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0",
b => b.UseNetTopologySuite());
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<House>()
.Property(e => e.Location)
.HasConversion(GeoPointConverter.Instance);
modelBuilder.HasDbFunction(
typeof(GeoExtensions).GetMethod(nameof(GeoExtensions.Distance)),
b => b.HasTranslation(
e => new SqlFunctionExpression(
e.First(),
"STDistance",
typeof(double),
e.Skip(1))));
}
}
public class ReplacementTypeMappingSource : SqlServerTypeMappingSource
{
public ReplacementTypeMappingSource(
TypeMappingSourceDependencies dependencies,
RelationalTypeMappingSourceDependencies relationalDependencies)
: base(dependencies, relationalDependencies)
{
}
protected override RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo)
=> mappingInfo.ClrType == typeof(GeoPoint)
|| "geography".Equals(mappingInfo.StoreTypeName, StringComparison.OrdinalIgnoreCase)
? (RelationalTypeMapping)base.FindMapping(typeof(IGeometry))
.Clone(GeoPointConverter.Instance)
: base.FindMapping(mappingInfo);
}
public class Program
{
public static void Main()
{
using (var context = new BloggingContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
context.Add(new House { Location = new GeoPoint(-122.34877, 47.6233355) });
context.Add(new House { Location = new GeoPoint(-122.3308366, 47.5978429) });
context.SaveChanges();
}
using (var context = new BloggingContext())
{
var distances = context
.Set<House>().Select(e => e.Location.Distance(new GeoPoint(-121.3308366, 46.5978429)))
.ToList();
}
}
} |
Did you mean 2.2? |
@ajcvickers Since 2.2 is out I tried your workaround. Unfortunately, I get the following exception:
|
@andriysavin Unfortunately there were additional complexities in the function mapping design that meant we were not able to make this work for 2.2. |
@ajcvickers You mean even the workaround? |
@andriysavin Yes. |
@pmiddleton - I vaguely remember you had API to configure parameters in UDFs which we stripped out before committing. Do you remember or should I find the PR? |
@smitpatel - I originally did have the ability to add parameters. I can't find the version that was part of that PR. I best I have been able to find is an older version when the code was still in the main EFCore assembly and You can see that here. I think the version that got converted to be in EFCore.Relational might be lost. Is there something I can help you with? |
That should be fine. Basically, for this issue, we need ability to configure parameter's column type on server side. Something like, |
@pmiddleton We're still interested in this functionality, so if you have ideas we would be happy to discuss/consider PRs. |
@ajcvickers - I dug around on my pc and found the rest of the db function parameter code. I will work on a PR to get the parameter logic added back in. Assuming I ever thaw out again.... -28 degrees here. |
@smitpatel @ajcvickers, @divega I just pulled latest and it appears the build system has changed a bit for 3.0 and I can't get the build script to work. When I try to run the build script I just get a "build failure" error with no reason why. I tried adding the -v flag but that just spit out the command line that failed. KoreBuild is installing the 3.0.100-preview-010010 sdk to a .dotnet directory local to the project (not in userprofile as it use to). I also tried building in VS and get the error "Unable to locate the .NET Core SDK. Check that it is installed and that the version specified in global.json (if any) matches the installed version." I've also tried making a fresh clone but that has the same issue. Is there something else that I need to setup, or a way to get a better error message to know what is failing? |
@pmiddleton Try running |
@andriysavin - Still no go on the build script. However opening EFCore.Runtime.sln did get me a different error. Version 3.0.100-preview-010010 of the .NET Core SDK requires at least version 16.0.0 of MSBuild. The current available version of MSBuild is 15.9.21.664. The version of msbuild.dll included in the 3.0 framework is 16. However it seems VS isn't wanting to load that version up. Maybe everything is just too frozen still from the polar vortex :) |
Do I need VS 2019 or will 2017 still work? |
Ok I got it. This was strange - I was missing %USERPROFILE%/.dotnet/NuGetFallbackFolder Once I added that empty folder the build script started working and then VS would build after that. |
VS 2019 is preferred, but VS 2017 should still work for a little while. |
Hi folks. I'm wondering if this was finally fixed in preview 7? Because I tried this sample project (after fixing NTS API breaking changes in latest version) without that
This is with preview 7. It might be important to quote this part which was affected by api change: modelBuilder.HasDbFunction(
typeof(GeoExtensions).GetMethod(nameof(GeoExtensions.Distance)),
b => b.HasTranslation(
e => new SqlFunctionExpression(
e.First(),
"STDistance",
e.Skip(1),
typeof(double),
typeMapping: null))); Pay attention to BTW, the workaround still doesn't work as well, returning me some SQL Server-side error on saving changes:
|
@smitpatel any comments on #13752 (comment) is this something that was supposed to be addressed by the fix or are additional calls required, e.g. to explicitly configure the store type. |
|
@smitpatel Wow, that function mapping works (doesn't fail during configuration), thanks! I have one usability question, though. modelBuilder.HasDbFunction(
typeof(GeoExtensions).GetMethod(nameof(GeoExtensions.Distance)),
builder =>
{
builder.HasName("STDistance");
builder.HasParameter("x").HasStoreType("geography");
builder.HasParameter("y").HasStoreType("geography");
}); Now, the bad part. When I execute the following simple query (from the sample code above) var distances = context
.Houses.Select(e => e.Location.Distance(new GeoPoint(-121.3308366, 46.5978429)))
.ToList(); on a DB without any rows I get this error:
I feel that this is related to the The place where it happens: The stack trace:
|
The thing is EF Core does not know how to do the conversion.
As you noted, 2nd option is not great experience compared to the first. |
@smitpatel Thanks for explaining! I think that both ways you mentioned would be helpful depending on situation. Any hope to see at least one of them implemented in 3.0? (I noted that the referenced issue mentions properties only, which I believe is not exactly what's needed). Also with using the syntax like
While using the |
Regarding the second error I was mentioning, SQL Server-side error on saving changes or on a query (with using the workaround):
I finally found out that it was result of incorrectly specifying latitude and longitude. After swapping them the error doesn't appear anymore. Though I'm surprised that there was no validation error that latitude was out of allowed range (-120). |
|
Thank you once more :) BTW, regarding configuring method parameters, to avoid referencing parameter names a good alternative could be using an approach similar to what different test mock frameworks do, e.g. with Moq: mock.Setup(foo => foo.Add(It.Is<int>(i => i % 2 == 0))).Returns(true); E.g: modelBuilder.HasDbFunction(() => GeoExtensions.Distance(
Has.Parameter<GeoPoint>().HasStoreType("geography"),
Has.Parameter<GeoPoint>().HasStoreType("geography"))); or modelBuilder.HasDbFunction(() => GeoExtensions.Distance(
Has.Parameter<GeoPoint>(pb => pb.HasStoreType("geography")),
Has.Parameter<GeoPoint>(pb => pb.HasStoreType("geography")))); |
See #13736 (comment) for the setup and the conversion.
I attempted to map my own function like this:
which is based on the GeoPoint mapping in the comment linked above.
My attempt was this:
This failed here because there is no mapping registered for GeoPoint.
This would likely be fixed by #10784, but even then it probably should be possible to make work with a custom mapping.
The text was updated successfully, but these errors were encountered: