簡體   English   中英

Entity Framework Core:此平台不支持 Udt 類型。 (空間數據 - 地理)

[英]Entity Framework Core: Type Udt is not supported on this platform. (Spatial Data - Geography)

我正在試驗實體框架核心,偶然發現了一個我以前從未見過的錯誤,無法弄清楚如何修復它。 我正在使用 .net Core Web API 2.0 和 EntityFramework Core 2.00-preview2-final

這是一個觸發錯誤的簡單示例。

(概念:從數據庫中獲取用戶的簡單端點)

錯誤:System.PlatformNotSupportedException:此平台不支持 Udt 類型。

有什么建議嗎?

問題是我在我的數據庫中使用 geography 但我在我的模型中將它用作字符串,因為實體框架核心還不支持空間數據......

有什么方法可以在不擺脫地理因素的情況下保持這個蛋糕的美味,因為它是一個重要的特征?

編輯:請參閱我對當前解決方案的回答

好的,這是我解決它的方法:

目的是在 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<User>().Ignore(x => x.Location);
}

4)生成遷移(Add-Migration migrationname)

5) 轉到您的遷移文件 1231randomnumbers1231_migrationname.cs 並添加以下內容(這樣我們創建另一列名為 Location 的 geography 類型),然后更新您的數據庫(update-database):

migrationBuilder.Sql(@"ALTER TABLE [dbo].[User] ADD [Location] geography NULL");

6)(可選)我創建了一個靜態類來更新數據庫,如果您在多個表中有一個位置列,這很方便。

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<Location> 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<User> 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<User> 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<User> UpdateAsync(User entity)
{
    ctx.User.Attach(entity);
    ctx.Entry<User>(entity).State = EntityState.Modified;
    await ctx.SaveChangesAsync();

    await GeneralDB.UpdateLocation(ctx, "user", entity.Location, entity.Id);

    return entity;
}

更新:從 EF Core 2.2 開始支持空間數據!:

https://docs.microsoft.com/en-us/ef/core/modeling/spatial


——

Eli,tnx 為您提供解決方案。 對我來說,這幾乎是完美的解決方案。 我有兩個問題:

問題

  1. 另一個應用程序也直接插入數據庫(臨時解決方案,將來會更改)。
  2. 獲取前 50 個實體時,數據必須按距離排序,因此將返回最近的 50 個實體。

解決方案

  1. 我沒有從代碼更新位置表,而是在訪問表上使用觸發器。 此觸發器將填充或插入、刪除或更新位置表。 因此,創建、更新、刪除功能除了保存實體外無需執行任何其他操作。

    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
  1. 獲取前 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<Visit> GetByIdAsync(int id)
    {
        return await _context.Visit.AsNoTracking().SingleOrDefaultAsync(x => x.Id == id);
    }



     public IList<Visit> 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<Visit> CreateAsync(Visit visit)
    {
        _context.Visit.Add(visit);
        await _context.SaveChangesAsync();
        return visit;  
    }

更新



    public async Task<Visit> 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 核心 2 目前不支持地理類型,您可以使用 NetTopologySuite 來支持所有服務器端地理類型。

當您有一個需要 geography 列的表時,添加 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。 如果您還需要 db 方面的地理支持(就像我們的團隊所做的那樣),那么您可以創建一個計算列,使用此數據生成正確的地理類型,如下所示:

ALTER TABLE dbo.tableName
    ADD Point AS 
        CONVERT(
            GEOGRAPHY,
            CASE WHEN [GeographyWkb] IS NOT NULL THEN 
                GEOGRAPHY::STGeomFromWKB ( [GeographyWkb], 4326 )
            ELSE NULL
            END)

EF 將忽略此計算列,您將能夠在 db 端使用它。 現在這確實讓您使用如何處理空間查詢,這留給讀者。 有多種方法可以處理它,上面的一些答案顯示了其中的一些。 值得注意的是,如果查詢很小,您可以使用 NetTopologySuite 在內存中進行,因為該庫提供了對聯合、交叉等的支持......

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM