2015-09-18 16 views
11

Possiedo un'applicazione multi-tenant che include un livello di servizio OData API Web. Ho un nuovo requisito per supportare campi personalizzati, che saranno unici per ciascun titolare, e l'aggiunta di colonne generiche "customfield01", "customfield02" alle mie tabelle non è abbastanza flessibile.API Web OData V4 Tipi aperti: configurazione del contesto di controller e dati

Ho esplorato una serie di modi per descrivere e mantenere i dati personalizzati sul back-end, ma la parte più impegnativa sembra estendere i miei servizi odata per includere i campi personalizzati, in modo diverso, per ciascun titolare.

Il seguente collegamento descrive "Tipi aperte" in odata v4 con API Web:

http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/use-open-types-in-odata-v4

Il codice di esempio funziona bene e fornisce il comportamento proprietà dinamica ho bisogno i miei soggetti. Tuttavia, il codice raggiunge solo l'utilizzo di un elenco di valori codificati per il back-end. Non è del tutto chiaro come popolare le entità da un contesto di dati di Entity Framework.

Inizialmente, sembrava che fosse facile avere una vista specifica del titolare nel database, per ogni titolare, ma il problema è che le proprietà estese devono davvero essere "non orientate" dalle colonne, in chiave coppie di valori. Per questo motivo, mi chiedo se ho bisogno di un'entità separata per le proprietà di "estensione". Così, ho potuto avere qualcosa di simile per il mio Pocos:

public class Item 
{ 
    [Key] 
    public Guid ItemId { get; set; } 

    public Guid TenantId { get; set; } 

    // navigation property for the extension entity 
    public virtual ItemExtension ItemExtension { get; set; } 
} 

public class ItemExtension 
{ 
    [Key] 
    public Guid ItemId { get; set; }  

    // dynamic properties for the open type 
    public IDictionary<string, object> DynamicProperties { get; set; }} 
} 

Ma ancora una volta, la questione diventa come popolare questi oggetti con i dati dal mio contesto dati. Ancora una volta, ho pensato che avrei avuto la possibilità di aprire le colonne, ma questo non funziona perché potrei avere diversi tipi di dati (che mi importano) per ogni proprietà dinamica.

Così, ho davvero alcune domande:

  1. Fa il modello POCO sopra ha senso per quello che sto cercando di realizzare?
  2. Quale dovrebbe essere il mio codice ItemController apparire come per includere l'ItemExtension per tutti i verbi HTTP (GET, POST, PUT, PATCH, DELETE)
  3. Quale dovrebbe essere il mio contesto dati hanno per l'ItemExtension per consentirgli di accedere alle colonne estesi su il back-end
  4. Come dovrebbero essere mantenute le colonne estese sul back-end per supportare questo.

Per quanto riguarda quello che ho provato - un sacco di cose che non funzionano, ma ho optato per il seguente (supponendo che non c'è un modo migliore):

  1. una base POCO per ogni entità "estendibile" con una "estensione" separata entità per ogni (come il modello precedente)

  2. sul back-end, dal momento che ho bisogno flexiblity illimitato e forti i tipi di dati, ho intenzione di avere un tabella di estensione separata per ogni combinazione di titolare/entità (sarebbe denominata [TenantId]. [ItemExtension] wi ogni colonna nominata e digitata come necessario).

Quello che mi manca è tutto tra i miei dati e il mio modello. Qualsiasi aiuto sarebbe molto apprezzato.

+0

Grande questione, che sono davvero bloccato ora, qualcuno ha usato con successo OData v4 Open Types con backend EF6? Nota: non NHibernate ... alla ricerca di una soluzione EF6 Prenderò prima il codice o il modello –

+0

Quello che sto facendo attualmente è utilizzare la connessione del contesto EF per eseguire una stored procedure. Quindi, devo scorrere i dati per creare il tipo aperto. Funziona, vorrei solo non dover "elaborare" i dati nel controller prima di restituirlo al chiamante. –

+0

Si è verificato un problema leggermente diverso da questo, ho funzionato tutto eccetto che qualsiasi chiamata GET che non specificava un'istruzione $ select falliva quando il runtime ha provato ad applicare le queryoptions che sono state passate in ... Il mio problema era che quando non si specifica $ select, questo è lo stesso di una selezione SQL *, solo il runtime inietta tutte le colonne nei parametri select, incluso DynamicProperties. Ho dovuto ignorare EnableQuery ... Non sono sicuro che si possa andare in giro a dover "elaborare" i dati, altrimenti avremmo potuto anche codificare le proprietà personalizzate nei DTO. –

risposta

0

Ora non utilizzo Entity Framework dopo il suo errore con i dati di memorizzazione nella cache.Guarda Fluent NHibernate. In esso ORM è possibile regolare la mappatura delle proprietà dinamiche OData v4 al tipo di utente. Usa il pacchetto nuget Newtonsoft.Json.

La classe:

public class Item 
{ 
    [Key] 
    public Guid ItemId { get; set; } 

    // dynamic properties for the open type 
    public IDictionary<string, object> DynamicProperties { get; set; } 

    ... 
} 

e StoreDynamicProperties tipo personalizzato della classe Fluent NHibernate:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Data; 
using System.Data.Common; 
using Newtonsoft.Json; 
using NHibernate.UserTypes; 
using NHibernate.SqlTypes; 

[Serializable] 
public class StoreDynamicProperties : IUserType 
{ 
    private JsonSerializerSettings _settings = new JsonSerializerSettings(); // { TypeNameHandling = TypeNameHandling.All }; 

    public new bool Equals(object x, object y) 
    { 
     if (x == null && y == null) 
      return true; 

     if (x == null || y == null) 
      return false; 

     var xdocX = JsonConvert.SerializeObject((IDictionary<string, object>)x, _settings); 
     var xdocY = JsonConvert.SerializeObject((IDictionary<string, object>)y, _settings); 

     return xdocY == xdocX; 
    } 

    public int GetHashCode(object x) 
    { 
     if (x == null) 
      return 0; 

     return x.GetHashCode(); 
    } 

    public object NullSafeGet(IDataReader rs, string[] names, object owner) 
    { 
     if (names.Length != 1) 
      throw new InvalidOperationException("Only expecting one column…"); 

     var val = rs[names[0]] as string; 

     if (val != null && !string.IsNullOrWhiteSpace(val)) 
     { 
      return JsonConvert.DeserializeObject<IDictionary<string, object>>(val, _settings); 
     } 

     return null; 
    } 

    public void NullSafeSet(IDbCommand cmd, object value, int index) 
    { 
     var parameter = (DbParameter)cmd.Parameters[index]; 

     if (value == null) 
     { 
      parameter.Value = DBNull.Value; 
     } 
     else 
     { 
      parameter.Value = JsonConvert.SerializeObject((IDictionary<string, object>)value, _settings); 
     } 
    } 

    public object DeepCopy(object value) 
    { 
     if (value == null) 
      return null; 

     //Serialized and Deserialized using json.net so that I don't 
     //have to mark the class as serializable. Most likely slower 
     //but only done for convenience. 

     var serialized = JsonConvert.SerializeObject((IDictionary<string, object>)value, _settings); 

     return JsonConvert.DeserializeObject<IDictionary<string, object>>(serialized, _settings); 
    } 

    public object Replace(object original, object target, object owner) 
    { 
     return original; 
    } 

    public object Assemble(object cached, object owner) 
    { 
     var str = cached as string; 

     if (string.IsNullOrWhiteSpace(str)) 
      return null; 

     return JsonConvert.DeserializeObject<IDictionary<string, object>>(str, _settings); 
    } 

    public object Disassemble(object value) 
    { 
     if (value == null) 
      return null; 

     return JsonConvert.SerializeObject((IDictionary<string, object>)value); 
    } 

    public SqlType[] SqlTypes 
    { 
     get 
     { 
      return new SqlType[] { new StringSqlType(8000) }; 
     } 
    } 

    public Type ReturnedType 
    { 
     get { return typeof(IDictionary<string, object>); } 
    } 

    public bool IsMutable 
    { 
     get { return true; } 
    } 
} 

e in classe ItemMap:

using FluentNHibernate.Mapping; 

public class ItemMap : ClassMap<Item> 
{ 
    public ItemMap() 
    { 
     Table("Items"); 

     Id(item => item.ItemId) 
      .GeneratedBy 
      .GuidComb(); 

     Map(item => item.DynamicProperties) 
      .CustomType<StoreDynamicProperties>() 
      .Column("Properties") 
      .CustomSqlType("varchar(8000)") 
      .Length(8000); 
     ... 
    } 
} 
Problemi correlati