entity framework核心:此平台不支持类型Udt。 (空间数据 – 地理)
我正在尝试entity framework核心,偶然发现了一个我从未见过的错误,无法弄清楚如何修复它。 我正在使用.net Core Web API 2.0和EntityFramework Core 2.00-preview2-final
这是一个触发错误的简单示例。
(概念:从数据库中获取用户的简单端点)
错误:System.PlatformNotSupportedException:此平台不支持类型Udt。
有什么建议?
问题是我在我的数据库中使用地理,但我在模型中将它用作字符串,因为entity framework核心还不支持空间数据……
如何在不摆脱地理位置的情况下保持这种蛋糕的美味,这是一个重要特征吗?
编辑:请参阅我当前解决方案的答案
好的,我是如何解决它的:
目的是将地理位置保留在Entity Framework Core中(不使用DbGeography)
1)我创建了一个名为Location的结构:
public struct Location { public double Longitude { get; set; } public double Latitude { get; set; } }
2)将其添加到您的EF实体模型
public class User { public Location Location { get; set; } }
3)在模型构建器中隐藏它
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().Ignore(x => x.Location); }
4)生成迁移(添加迁移迁移名称)
5)转到您的迁移文件1231randomnumbers1231_migrationname.cs并添加以下内容(这样我们创建另一个名为Location的地理类型列),然后更新您的数据库(update-database):
migrationBuilder.Sql(@"ALTER TABLE [dbo].[User] ADD [Location] geography NULL");
6)(可选)我创建了一个静态类来更新数据库,如果在mulple表中有一个Location列,则会很方便。
public static class GeneralDB { public static async Task UpdateLocation(DbContext ctx, string table, Location location, int id) { Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US"); string query = String.Format(@"UPDATE [dbo].[{0}] SET Location = geography::STPointFromText('POINT(' + CAST({1} AS VARCHAR(20)) + ' ' + CAST({2} AS VARCHAR(20)) + ')', 4326) WHERE(ID = {3})" , table.ToLower(), location.Longitude, location.Latitude, id); await ctx.Database.ExecuteSqlCommandAsync(query); } public static async Task GetLocation(DbContext ctx, string table, int id) { Location location = new Location(); using (var command = ctx.Database.GetDbConnection().CreateCommand()) { string query = String.Format("SELECT Location.Lat AS Latitude, Location.Long AS Longitude FROM [dbo].[{0}] WHERE Id = {1}" , table, id); command.CommandText = query; ctx.Database.OpenConnection(); using (var result = command.ExecuteReader()) { if (result.HasRows) { while (await result.ReadAsync()) { location.Latitude = result.GetDouble(0); location.Longitude = result.GetDouble(1); } } } } return location; } }
这仅适用于EF Core 2.0
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
对于EF Core 1.0,您必须找到另一种方法来将’,’替换为’。’。 一个很好的旧时尚.Replace()方法可以完成这项工作。
location.Longitude.ToString().Replace(',', '.')
7)CRUD示例:
7.1:阅读
public async Task GetByIdAsync(int id) { User user = await ctx.User.AsNoTracking().SingleOrDefaultAsync(x => x.Id == id); user.Location = await GeneralDB.GetLocation(ctx, "user", id); return user; }
7.2:创建
public async Task CreateAsync(User entity) { ctx.User.Add(entity); await ctx.SaveChangesAsync(); await GeneralDB.UpdateLocation(ctx, "user", entity.Location, entity.Id); return entity; }
7.3:更新
public async Task UpdateAsync(User entity) { ctx.User.Attach(entity); ctx.Entry (entity).State = EntityState.Modified; await ctx.SaveChangesAsync(); await GeneralDB.UpdateLocation(ctx, "user", entity.Location, entity.Id); return entity; }
Eli,tnx为你提供解决方案。 对我来说,这几乎是完美的解决方案。 我有两个问题:
问题
- 另一个应用程序也直接插入数据库(临时解决方案,将来会更改)。
- 获取前50个实体时,数据必须按距离排序,因此将返回50个最近的实体。
解决方案
- 我没有在代码中更新位置表,而是使用访问表上的触发器。 此触发器将填充或插入,删除或更新Location表。 因此,创建,更新,删除function除了保存实体之外不必执行任何操作。
create trigger VisitLocation_trigger on Visit after UPDATE, INSERT, DELETE as if exists(SELECT * from inserted) If exists(Select * from deleted) BEGIN -- UPDATE UPDATE visit_location SET location = GEOGRAPHY::Point(Latitude, Longitude, 4326) FROM visit_location JOIN inserted ON visit_location.visitid = inserted.id END else BEGIN -- INSERT INSERT INTO visit_location SELECT Id, GEOGRAPHY::Point(Latitude, Longitude, 4326) FROM inserted END else BEGIN -- DELETE declare @visitId int; SELECT @visitId = Id from deleted i; DELETE visit_location WHERE visit_location.visitid = @visitId end
- 获取前50个查询必须是原始SQL查询,如下所示:
_context.Visit.FromSql( "SELECT TOP 50 v.* " + "FROM visit v " + "INNER JOIN visit_location vl ON v.id = vl.visitid " + "WHERE v.date > {0} " + "AND GEOGRAPHY::Point({1},{2}, 4326).STDistance(Location) < {3} " + "ORDER BY GEOGRAPHY::Point({1},{2}, 4326).STDistance(Location)", startDate, latitude, longitude, radius).ToList();
CRUD
读
public async Task GetByIdAsync(int id) { return await _context.Visit.AsNoTracking().SingleOrDefaultAsync(x => x.Id == id); }
public IList GetLastVisitsForHouseIdsByCoordinates(DateTime startDate, double longitude, double latitude, long radius) { return _context.Visit.FromSql("SELECT TOP 50 v.* " + "FROM visit v " + "INNER JOIN visit_location vl ON v.id = vl.visitid " + "WHERE v.IsLastVisit = 1 " + "AND v.date > {0} " + "AND GEOGRAPHY::Point({1},{2}, 4326).STDistance(Location) < {3} " + "ORDER BY GEOGRAPHY::Point({1},{2}, 4326).STDistance(Location)", startDate, latitude, longitude, radius).ToList(); }
创建
public async Task CreateAsync(Visit visit) { _context.Visit.Add(visit); await _context.SaveChangesAsync(); return visit; }
更新
public async Task UpdateAsync(Visit visit) { _context.Visit.Attach(visit); _context.Entry(visit).State = EntityState.Modified; await _context.SaveChangesAsync(); return visit; }
删除
public async Task DeleteAsync(Visit visit) { _dbContext.Remove(entityToUpdate); _context.Entry(visit).State = EntityState.Deleted; await _context.SaveChangesAsync(); return visit; }
数据库模型
public class Visit { public int Id { get; set; } [Required] public VisitStatus Status { get; set; } [Required] public double? Latitude { get; set; } [Required] public double? Longitude { get; set; } public Location Location { get; set; } [Required] public DateTime Date { get; set; } public string Street { get; set; } public int? StreetNumber { get; set; } public string StreetNumberLetter { get; set; } public string StreetNumberLetterAddition { get; set; } public string City { get; set; } } public struct Location { public double Longitude { get; set; } public double Latitude { get; set; } }
这些解决方案有效,但如果您正在寻找其他方法,这是另一种解决方案。 由于EF core 2目前不支持地理类型,因此您可以将NetTopologySuite用于所有服务器端地理支持。
如果您有一个需要地理列的表添加属性,EF可以映射到您的表,其类型为byte []或string。 像这样:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using NetTopologySuite; using NetTopologySuite.Geometries; namespace GeoTest2.Data { public class HomeTown { private byte[] _pointsWKB; public string Name { get; set; } public byte[] PointWkb { get => _pointsWKB; set { if (GeopgraphyFactory.CreatePoint(value) != null) _pointsWKB = value; throw new NotImplementedException("How ever you wnat to handle it"); } } [NotMapped] public Point Point { get => GeopgraphyFactory.CreatePoint(_pointsWKB); set => _pointsWKB = GeopgraphyFactory.PointAsWkb(value); } } }
这使用一些助手来创建点在这里:
using NetTopologySuite.Geometries; namespace GeoTest2.Data { public static class GeopgraphyFactory { public static Point CreatePoint(byte[] wkb) { var reader = new NetTopologySuite.IO.WKBReader(); var val = reader.Read(wkb); return val as Point; } public static byte[] PointAsWkb(Point point) { var writer = new NetTopologySuite.IO.WKBWriter(); return writer.Write(point); } } }
你可以看到这里没有什么特别之处。 这段代码应该有完整的CRUDS。 如果您还需要数据库端的地理支持(就像我们的团队那样),那么您可以创建一个计算列,使用此数据生成正确的地理类型,如下所示:
ALTER TABLE dbo.tableName ADD Point AS CONVERT( GEOGRAPHY, CASE WHEN [GeographyWkb] IS NOT NULL THEN GEOGRAPHY::STGeomFromWKB ( [GeographyWkb], 4326 ) ELSE NULL END)
EF将忽略此计算列,您将能够使用它的数据库侧。 现在,这确实可以用于处理空间查询,并留给读者。 有很多方法可以处理它,上面的一些答案显示了其中一些。 值得注意的是,如果查询很小,你可以使用NetTopologySuite在内存中进行,因为库提供了对工会,交叉点等的支持……
- 带有电子邮件地址参数的WebApi方法从HttpClient返回404
- 将对象序列化为已包含一个JSON属性的JSON
- .NET Core – 尝试将存储库添加到我的API控制器,但是当我执行每个控制器方法时都会返回500错误
- 客户端已断开连接
- Ninject在具有多个程序集的WebApi项目中抛出Activation Exception
- 在Web API中使用ExceptionFilterAttribute
- 改变facebook redirect_uri web api
- entity framework在Web api / MVC中使用异步控制器进行处理
- 从Javascript将文件对象传递给Web API