Sto sperimentando con il core della struttura dell'entità e sono incappato in un errore che non ho mai visto prima e non riesco a capire come risolverlo. Sto usando .net Core Web API 2.0 con EntityFramework Core 2.00-preview2-final
Ecco un semplice esempio che attiva l'errore.
(concetto: semplice endpoint per ottenere un utente dal database)
Errore: System.PlatformNotSupportedException: Type Udt non è supportato su questa piattaforma.
Eventuali suggerimenti?
Il problema è che sto usando la geografia nel mio database ma la uso come una stringa nel mio modello, perché il core della struttura dell'entità non supporta ancora i dati spaziali ...
Un modo per mantenere gustosa questa torta senza sbarazzarsi della geografia, perché è una caratteristica importante?
Modifica: vedere la mia risposta per la soluzione corrente
Ok ecco come l'ho risolto:
Lo scopo è di mantenere la geografia in Entity Framework Core (senza usare DbGeography)
1) Ho creato una struttura chiamata Location:
public struct Location
{
public double Longitude { get; set; }
public double Latitude { get; set; }
}
2) Aggiungilo al tuo modello di entità EF
public class User
{
public Location Location { get; set; }
}
3) Nascondilo nel tuo modellista
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().Ignore(x => x.Location);
}
4) Genera una migrazione (Add-Migration migrationname)
5) Vai al tuo file di migrazione 1231randomnumbers1231_migrationname.cs e aggiungi quanto segue (in questo modo creiamo un'altra colonna di tipo geografia denominata Location) e quindi aggiorni il tuo database (update-database):
migrationBuilder.Sql(@"ALTER TABLE [dbo].[User] ADD [Location] geography NULL");
6) (facoltativo) Ho creato una classe statica per aggiornare il db, utile se hai una colonna Location in molte tabelle.
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;
}
}
Funziona solo con EF Core 2.0
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
Per EF Core 1.0 dovresti trovare un modo alternativo per sostituire un ',' con '.'. Un buon vecchio metodo. Il metodo Replace () potrebbe fare il lavoro.
location.Longitude.ToString().Replace(',', '.')
7) Esempi di CRUD:
7.1: Leggi
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: Crea
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: Aggiornamento
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;
}
AGGIORNAMENTO: dal momento che EF Core 2.2 supporta i dati spaziali !:
http: // portainer / # / pile / kolibry-acc_8ssg5qantkr2dgrbxsamsikf6
Eli, per te la soluzione. Per me era quasi la soluzione perfetta. Ho avuto 2 problemi:
I problemi
soluzioni
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
Leggere
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();
}
Creare
public async Task<Visit> CreateAsync(Visit visit)
{
_context.Visit.Add(visit);
await _context.SaveChangesAsync();
return visit;
}
Aggiornare
public async Task<Visit> UpdateAsync(Visit visit)
{
_context.Visit.Attach(visit);
_context.Entry(visit).State = EntityState.Modified;
await _context.SaveChangesAsync();
return visit;
}
Elimina
public async Task DeleteAsync(Visit visit)
{
_dbContext.Remove(entityToUpdate);
_context.Entry(visit).State = EntityState.Deleted;
await _context.SaveChangesAsync();
return visit;
}
I modelli di database
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; }
}