Ich experimentiere mit Entity Framework Core und stolperte über einen Fehler, den ich noch nie zuvor gesehen habe und kann nicht herausfinden, wie ich es beheben kann. Ich verwende .net Core Web API 2.0 mit EntityFramework Core 2.00-preview2-final
Hier ist ein einfaches Beispiel, das den Fehler auslöst.
(Konzept: einfacher Endpunkt, um einen Benutzer aus der Datenbank zu holen)
Fehler: System.PlatformNotSupportedException: Type Udt wird auf dieser Plattform nicht unterstützt.
Irgendwelche Vorschläge?
Das Problem ist, dass ich Geografie in meiner Datenbank verwende, aber ich verwende es als eine Zeichenfolge in meinem Modell, weil der Entity Framework-Core noch keine räumlichen Daten unterstützt ...
Irgendeine Möglichkeit, diesen Kuchen lecker zu halten, ohne die Geographie loszuwerden, weil es ein wichtiges Merkmal ist?
Bearbeiten: Siehe meine Antwort für die aktuelle Lösung
Ok, hier habe ich es gelöst:
Der Zweck besteht darin, die Geographie im Entity Framework Core zu halten (ohne DbGeography zu verwenden).
1) Ich habe eine Struktur namens Location erstellt:
public struct Location
{
public double Longitude { get; set; }
public double Latitude { get; set; }
}
2) Fügen Sie es Ihrem EF-Entity-Modell hinzu
public class User
{
public Location Location { get; set; }
}
3) Verstecken Sie es in Ihrem Modellbauer
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().Ignore(x => x.Location);
}
4) Erzeuge eine Migration (Add-Migration Migrationsname)
5) Gehen Sie zu Ihrer Migrationsdatei 1231randomnumbers1231_migrationname.cs und fügen Sie Folgendes hinzu (auf diese Weise erstellen wir eine weitere Spalte vom Typ geography namens Location) und aktualisieren Sie dann Ihre Datenbank (update-database):
migrationBuilder.Sql(@"ALTER TABLE [dbo].[User] ADD [Location] geography NULL");
6) (optional) Ich habe eine statische Klasse zur Aktualisierung der Datenbank erstellt, praktisch, wenn Sie in mehreren Tabellen eine Standortspalte haben.
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;
}
}
Dies funktioniert nur in EF Core 2.0
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
Für EF Core 1.0 müssten Sie einen alternativen Weg finden, ein ',' durch '.' Zu ersetzen. Eine gute alte Mode. Replace () -Methode könnte den Job machen.
location.Longitude.ToString().Replace(',', '.')
7) CRUD Beispiele:
7.1: Lesen
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: Erstellen
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: Aktualisierung
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;
}
UPDATE: Seit EF Core 2.2 gibt es Unterstützung für Geodaten !:
http: // portainer / # / stacks / kolibry-acc_8ssg5qantkr2dgrbxsamsikf6
Eli, tnx für deine Lösung. Für mich war es fast die perfekte Lösung. Ich hatte 2 Probleme:
Probleme
Lösungen
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
_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
Lesen
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();
}
Erstellen
public async Task<Visit> CreateAsync(Visit visit)
{
_context.Visit.Add(visit);
await _context.SaveChangesAsync();
return visit;
}
Aktualisieren
public async Task<Visit> UpdateAsync(Visit visit)
{
_context.Visit.Attach(visit);
_context.Entry(visit).State = EntityState.Modified;
await _context.SaveChangesAsync();
return visit;
}
Löschen
public async Task DeleteAsync(Visit visit)
{
_dbContext.Remove(entityToUpdate);
_context.Entry(visit).State = EntityState.Deleted;
await _context.SaveChangesAsync();
return visit;
}
Die Datenbankmodelle
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; }
}