2016-06-23 92 views
15

Sono stato in giro con XML s per Entità Framework. Ho cercato di creare un tipo di entità che avrebbero potuto proprietà iniettato in fase di esecuzione, prima cosa creata DynamicEntity oggetto che è dinamicoEntity Framework Entity non è in DataSpace.OSpace (_workspace.GetItemCollection (DataSpace.OSpace)) ma è in DataSpace.CSpace

public class DynamicEntity : DynamicObject 
{ 
    Dictionary<string, object> dynamicMembers = new Dictionary<string, object>(); 

    public override bool TrySetMember(SetMemberBinder binder, object value) 
    { 
     dynamicMembers[binder.Name] = value; 
     return true; 
    } 

    public override bool TryGetMember(GetMemberBinder binder, out object result) 
    { 
     if (dynamicMembers.TryGetValue(binder.Name, out result)) 
     { 
      return dynamicMembers.TryGetValue(binder.Name, out result); 
     } 

     result = ""; 
     return true; 
    } 
} 

quindi entità eredita da questo

public partial class QUOTE_HOUSE : DynamicEntity

(e lo fa sembra funzionare quando imposto le proprietà manualmente dopo aver ottenuto i dati da db).

so based on this mechanism of removing properties Ho provato a fare un altro che inserisce proprietà in XML, e l'intera cosa sembra reggere bene (almeno non esplodere sulla mappatura che di solito fa quando XML non è giusto var mappingCollection = new StorageMappingItemCollection(conceptualCollection, storageCollection, new[] {mappingXml.CreateReader()});).

problema è EF durante l'esecuzione di query soffia con

Il tipo QUOTE_HOUSE entità non è parte del modello per il contesto corrente.

Descrizione: si è verificata un'eccezione non gestita durante l'esecuzione di la richiesta Web corrente. Si prega di rivedere lo stack trace per ulteriori informazioni sull'errore e dove è stato originato nel codice.

Dettagli eccezione: System.InvalidOperationException: il tipo di entità QUOTE_HOUSE non fa parte del modello per il contesto corrente.

[InvalidOperationException:. Il tipo di entità QUOTE_HOUSE non fa parte modello per il contesto corrente]
System.Data.Entity.Internal.InternalContext.UpdateEntitySetMappingsForType (Tipo entityType) +208
System.Data. Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType (tipo entityType) +50

che ho tracciato per TryUpdateEntitySetMappingsForType in System.Data.Entity.Internal.InternalContext dopo il caricamento PPB per EF

Fondamentalmente cosa succede il mio QUOTE_HOUSE non è in this._workspace.GetItemCollection(DataSpace.OSpace) dove UpdateEntitySetMappings tenta di mapparlo da.

controlla se è in this._entitySetMappingsCache.ContainsKey(entityType)) e dal momento che non è poi cerca mappature aggiornamento iterazione di this._workspace.GetItemCollection(DataSpace.OSpace) dove la mia voce non esiste

Tuttavia posso vedere che il mio soggetto esiste in this._workspace.GetItems<EntityContainer>(DataSpace.CSpace).

completa UpdateEntitySetMappings aspetto seguente:

private void UpdateEntitySetMappings() 
{ 
    ObjectItemCollection objectItemCollection = (ObjectItemCollection) this._workspace.GetItemCollection(DataSpace.OSpace); 
    ReadOnlyCollection<EntityType> items = this._workspace.GetItems<EntityType>(DataSpace.OSpace); 
    Stack<EntityType> entityTypeStack = new Stack<EntityType>(); 
    foreach (EntityType entityType1 in items) 
    { 
    entityTypeStack.Clear(); 
    EntityType cspaceType = (EntityType) this._workspace.GetEdmSpaceType((StructuralType) entityType1); 
    do 
    { 
     entityTypeStack.Push(cspaceType); 
     cspaceType = (EntityType) cspaceType.BaseType; 
    } 
    while (cspaceType != null); 
    EntitySet entitySet = (EntitySet) null; 
    while (entitySet == null && entityTypeStack.Count > 0) 
    { 
     cspaceType = entityTypeStack.Pop(); 
     foreach (EntityContainer entityContainer in this._workspace.GetItems<EntityContainer>(DataSpace.CSpace)) 
     { 
     List<EntitySetBase> list = entityContainer.BaseEntitySets.Where<EntitySetBase>((Func<EntitySetBase, bool>) (s => s.ElementType == cspaceType)).ToList<EntitySetBase>(); 
     int count = list.Count; 
     if (count > 1 || count == 1 && entitySet != null) 
      throw Error.DbContext_MESTNotSupported(); 
     if (count == 1) 
      entitySet = (EntitySet) list[0]; 
     } 
    } 
    if (entitySet != null) 
    { 
     EntityType entityType2 = (EntityType) this._workspace.GetObjectSpaceType((StructuralType) cspaceType); 
     Type clrType1 = objectItemCollection.GetClrType((StructuralType) entityType1); 
     Type clrType2 = objectItemCollection.GetClrType((StructuralType) entityType2); 
     this._entitySetMappingsCache[clrType1] = new EntitySetTypePair(entitySet, clrType2); 
    } 
    } 
} 

Come entità entrare in this._workspace.GetItemCollection (DataSpace.OSpace)? Perché l'entità dovrebbe essere in CSpace ma non in OSpace?

EDIT: Per coloro che potrebbero desiderare di avere una crepa al di taglie, di seguito sono i componenti che potrebbe essere necessario set-up ambiente di riprodurre il problema.

public class SystemToDatabaseMapping 
{ 
    public SystemToDatabaseMapping(string system, string databaseType, string database, string connectionString, Type enitityType) 
    { 
     System = system; 
     Database = database; 
     DatabaseType = databaseType; 
     ConnectionString = connectionString; 
     EntityType = enitityType; 
    } 

    public Type EntityType { get; set; } 
    public string System { get; set; } 
    public string Database { get; set; } 
    public string DatabaseType { get; set; } 
    public string ConnectionString { get; set; } 
    public List<ColumnToModify> ColumnsToModify { get; set; } 
} 

public abstract class ColumnToModify 
{ 
    protected ColumnToModify(string table, string column) 
    { 
     Table = table; 
     Column = column; 
    } 

    public string Table { get; set; } 
    public string Column { get; set; } 

    public abstract bool IsRemove{ get; } 
} 

public class ColumnToRemove : ColumnToModify 
{ 
    public ColumnToRemove(string table, string column) : base(table, column) 
    { 
    } 

    public override bool IsRemove 
    { 
     get { return true; } 
    } 
} 

public class ColumnToAdd : ColumnToModify 
{ 
    public ColumnToAdd(string table, string column, Type type) : base(table, column) 
    { 
     this.Type = type; 
    } 

    public override bool IsRemove 
    { 
     get { return false; } 
    } 

    public Type Type { get; set; } 
} 

Entity generato dal primo db, (DynamicEntity codice è sopra)

public partial class QUOTE_HOUSE : DynamicEntity 
{ 
    public long UNIQUE_ID { get; set; } 
} 

DbContext per il database richiede costruttore sovraccarica

public partial class EcomEntities : DbContext 
{ 

    public EcomEntities(DbConnection connectionString) 
     : base(connectionString, false) 
    { 
    } 

    public virtual DbSet<QUOTE_HOUSE > QUOTE_HOUSE { get; set; } 
.... 
} 

meccanismo che fa iniezione colonna (è un prototipo grezzo così sii perdonatore di quanto sia terribile), quando iniettando provo una colonna di stringhe so che funziona bene.

public static class EntityConnectionExtensions 
{ 
    public static IEnumerable<XElement> ElementsAnyNS<T>(this IEnumerable<T> source, string localName) 
     where T : XContainer 
    { 
     return source.Elements().Where(e => e.Name.LocalName == localName); 
    } 

    public static IEnumerable<XElement> ElementsAnyNS(this XContainer source, string localName) 
    { 
     return source.Elements().Where(e => e.Name.LocalName == localName); 
    } 

    private static void ModifyNodes(XElement element, List<ColumnToModify> tableAndColumn) 
    { 
     if (element.Attribute("Name") != null && tableAndColumn.Any(oo => oo.Table == element.Attribute("Name").Value) || 
      element.Attribute("StoreEntitySet") != null && tableAndColumn.Any(oo => oo.Table == element.Attribute("StoreEntitySet").Value)) 
     { 
      var matchingRemoveSelectParts = tableAndColumn.Where(oo => oo.IsRemove && element.Value.Contains(string.Format("\"{0}\".\"{1}\" AS \"{1}\"", oo.Table, oo.Column))).ToList(); 

      if (matchingRemoveSelectParts.Any()) 
      { 
       foreach (var matchingRemoveSelectPart in matchingRemoveSelectParts) 
       { 
        var definingQuery = element.ElementsAnyNS("DefiningQuery").Single(); 
        definingQuery.Value = definingQuery.Value.Replace(string.Format(", \n\"{0}\".\"{1}\" AS \"{1}\"", matchingRemoveSelectPart.Table, matchingRemoveSelectPart.Column), ""); 
       } 
      } 
      else 
      { 
       var nodesToRemove = element.Nodes() 
        .Where(o => 
         o is XElement 
         && ((XElement) o).Attribute("Name") != null 
         && tableAndColumn.Any(oo => oo.IsRemove && ((XElement) o).Attribute("Name").Value == oo.Column)); 

       foreach (var node in nodesToRemove.ToList()) 
       { 
        node.Remove(); 
       } 

       if (element.Attribute("Name") != null && tableAndColumn.Any(oo => oo.Table == element.Attribute("Name").Value)) 
       { 
        var elementsToAdd = tableAndColumn.Where(o => !o.IsRemove && o.Table == element.Attribute("Name").Value); 
        if (new[] {"Type=\"number\"", "Type=\"varchar2\"", "Type=\"date\""}.Any(o => element.ToString().Contains(o))) 
        { 
         foreach (var columnToModify in elementsToAdd) 
         { 
          var columnToAdd = (ColumnToAdd) columnToModify; 

          var type = new[] {typeof (decimal), typeof (float), typeof (int), typeof (bool)}.Contains(columnToAdd.Type) 
           ? "number" 
           : columnToAdd.Type == typeof (DateTime) ? "date" : "varchar2"; 

          var precision = ""; 
          var scale = ""; 
          var maxLength = ""; 
          if (type == "number") 
          { 
           precision = "38"; 
           scale = new[] {typeof (decimal), typeof (float)}.Contains(columnToAdd.Type) ? "2" : "0"; 
          } 

          if (type == "varchar2") 
          { 
           maxLength = "500"; 
          } 

          var newProperty = new XElement(element.GetDefaultNamespace() + "Property", new XAttribute("Name", columnToAdd.Column), new XAttribute("Type", type)); 
          if (!string.IsNullOrWhiteSpace(precision)) 
          { 
           newProperty.Add(new XAttribute("Precision", precision)); 
          } 

          if (!string.IsNullOrWhiteSpace(scale)) 
          { 
           newProperty.Add(new XAttribute("Scale", scale)); 
          } 

          if (!string.IsNullOrWhiteSpace(maxLength)) 
          { 
           newProperty.Add(new XAttribute("MaxLength", maxLength)); 
          } 

          element.Add(newProperty); 
         } 
        } 
        else if (
         new[] {"Type=\"Decimal\"", "Type=\"String\"", "Type=\"DateTime\"", "Type=\"Boolean\"", "Type=\"Byte\"", "Type=\"Int16\"", "Type=\"Int32\"", "Type=\"Int64\""}.Any(
          o => element.ToString().Contains(o))) 
        { 
         foreach (var columnToModify in elementsToAdd) 
         { 
          var columnToAdd = (ColumnToAdd) columnToModify; 

          var type = new[] {typeof (decimal), typeof (float), typeof (int), typeof (bool)}.Contains(columnToAdd.Type) 
           ? "Decimal" 
           : columnToAdd.Type == typeof (DateTime) ? "DateTime" : "String"; 

          var precision = ""; 
          var scale = ""; 
          var maxLength = ""; 
          if (type == "Decimal") 
          { 
           precision = "38"; 
           scale = new[] {typeof (decimal), typeof (float)}.Contains(columnToAdd.Type) ? "2" : "0"; 
          } 

          if (type == "String") 
          { 
           maxLength = "500"; 
          } 

          var newProperty = new XElement(element.GetDefaultNamespace() + "Property", new XAttribute("Name", columnToAdd.Column), new XAttribute("Type", type)); 
          if (!string.IsNullOrWhiteSpace(precision)) 
          { 
           newProperty.Add(new XAttribute("Precision", precision)); 
          } 

          if (!string.IsNullOrWhiteSpace(scale)) 
          { 
           newProperty.Add(new XAttribute("Scale", scale)); 
          } 

          if (!string.IsNullOrWhiteSpace(maxLength)) 
          { 
           newProperty.Add(new XAttribute("MaxLength", maxLength)); 
           newProperty.Add(new XAttribute("FixedLength", "false")); 
           newProperty.Add(new XAttribute("Unicode", "false")); 
          } 

          element.Add(newProperty); 
         } 
        } 
       } 
      } 

      if (element.Attribute("Name") != null && tableAndColumn.Any(oo => oo.Table == element.Attribute("Name").Value) && element.GetNamespaceOfPrefix("store") != null && 
       element.Attribute(element.GetNamespaceOfPrefix("store") + "Type") != null && 
       element.Attribute(element.GetNamespaceOfPrefix("store") + "Type").Value == "Tables") 
      { 
       var matchingAddSelectParts = tableAndColumn.Where(o => !o.IsRemove && o.Table == element.Attribute("Name").Value); 
       foreach (var matchingAddSelectPart in matchingAddSelectParts) 
       { 
        var definingQuery = element.ElementsAnyNS("DefiningQuery").Single(); 
        var schemaRegex = new Regex(string.Format("\\nFROM \\\"([a-zA-Z0-9]*)\\\".\\\"{0}\\\"", matchingAddSelectPart.Table)); 
        var schema = schemaRegex.Matches(definingQuery.Value)[0].Groups[1].Value; 
        definingQuery.Value = definingQuery.Value.Replace(
         string.Format("\nFROM \"{0}\".\"{1}\" \"{1}\"", schema, matchingAddSelectPart.Table), 
         string.Format(", \n\"{0}\".\"{1}\" AS \"{1}\"\nFROM \"{2}\".\"{0}\" \"{0}\"", matchingAddSelectPart.Table, matchingAddSelectPart.Column, schema)); 
       } 
      } 

      if (element.Attribute("StoreEntitySet") != null && tableAndColumn.Any(oo => !oo.IsRemove && oo.Table == element.Attribute("StoreEntitySet").Value)) 
      { 
       var matchingAddSelectParts = tableAndColumn.Where(o => !o.IsRemove && o.Table == element.Attribute("StoreEntitySet").Value); 
       foreach (var matchingAddSelectPart in matchingAddSelectParts) 
       { 
        element.Add(new XElement(element.GetDefaultNamespace() + "ScalarProperty", new XAttribute("Name", matchingAddSelectPart.Column), 
         new XAttribute("ColumnName", matchingAddSelectPart.Column))); 
       } 
      } 
     } 
    } 

    public static EntityConnection Create(List<ColumnToModify> tablesAndColumns, string connString) 
    { 
     var modelNameRegex = new Regex(@".*metadata=res:\/\/\*\/([a-zA-Z.]*).csdl|.*"); 
     var model = modelNameRegex.Matches(connString).Cast<Match>().SelectMany(o => o.Groups.Cast<Group>().Skip(1).Where(oo => oo.Value != "")).Select(o => o.Value).First(); 

     var conceptualReader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream(model + ".csdl")); 
     var mappingReader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream(model + ".msl")); 
     var storageReader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream(model + ".ssdl")); 

     var conceptualXml = XElement.Load(conceptualReader); 
     var mappingXml = XElement.Load(mappingReader); 
     var storageXml = XElement.Load(storageReader); 

     foreach (var entitySet in new[] {storageXml, conceptualXml}.SelectMany(xml => xml.Elements())) 
     { 
      if (entitySet.Attribute("Name").Value == "ModelStoreContainer") 
      { 
       foreach (var entityContainerEntitySet in entitySet.Elements()) 
       { 
        ModifyNodes(entityContainerEntitySet, tablesAndColumns); 
       } 
      } 

      ModifyNodes(entitySet, tablesAndColumns); 
     } 

     foreach (var entitySet in mappingXml.Elements().ElementAt(0).Elements()) 
     { 
      if (entitySet.Name.LocalName == "EntitySetMapping") 
      { 
       foreach (var entityContainerEntitySet in entitySet.Elements().First().Elements()) 
       { 
        ModifyNodes(entityContainerEntitySet, tablesAndColumns); 
       } 
      } 

      ModifyNodes(entitySet, tablesAndColumns); 
     } 

     var storageCollection = new StoreItemCollection(new [] {storageXml.CreateReader()}); 
     var conceptualCollection = new EdmItemCollection(new[] { conceptualXml.CreateReader() }); 
     var mappingCollection = new StorageMappingItemCollection(conceptualCollection, storageCollection, new[] {mappingXml.CreateReader()}); 

     var workspace = new MetadataWorkspace(); 

     workspace.RegisterItemCollection(conceptualCollection); 
     workspace.RegisterItemCollection(storageCollection); 
     workspace.RegisterItemCollection(mappingCollection); 
     var connectionData = new EntityConnectionStringBuilder(connString); 
     var connection = DbProviderFactories 
      .GetFactory(connectionData.Provider) 
      .CreateConnection(); 
     connection.ConnectionString = connectionData.ProviderConnectionString; 

     return new EntityConnection(workspace, connection); 
    } 
} 

inizializzazione:

public ActionResult QUOTE_HOUSE() 
    { 
     var onlineDocs = Enumerable.Empty<QUOTE_HOUSE>(); 
     var mappings = new List<SagaSystemToDatabaseMapping>{new SagaSystemToDatabaseMapping("x", "Oracle", "Db1", 
        "metadata=res://*/Ecom.Ecom.csdl|res://*/Ecom.Ecom.ssdl|res://*/Ecom.Ecom.msl;provider=Oracle.ManagedDataAccess.Client;provider connection string='...'", typeof(EcomEntities)) 
       { 
        ColumnsToModify = new List<ColumnToModify> { new ColumnToAdd("QUOTE_HOUSE","TESTCOL", typeof(string)) } 
       }}; 
     var entityConnection = EntityConnectionExtensions.Create(mappings[0].ColumnsToModify,mappings[0].ConnectionString); 
     using (var db = new EcomEntities(entityConnection)) 
     { 
      onlineDocs = db.QUOTE_HOUSE.Take(10); 
     } 

     return View("QUOTE_HOUSE", onlineDocs.ToList()); 
    } 

Si dovrebbe essere in grado di generare database Oracle da entità QUOTE_HOUSE e inserire alcuni valori fittizi, non credo che hai bisogno di una vista che soffia su .ToList(). Dopo aver generato il database aggiungi colonna aggiuntiva al database ma non il modello (alter table QUOTE_HOUSE add TESTCOL Varchar2(20)) - per avere una colonna nel database che viene iniettata in fase di esecuzione nel modello. Potrebbe anche essere necessario eseguire il debug di EF assemblies here's how to do it. Per favore fatemi sapere se avete bisogno di maggiori informazioni o mi sono perso qualcosa.

+0

Se qualcuno ha bisogno di ulteriori dettagli o aiuto nell'impostazione dell'ambiente, ho creato questo http://chat.stackexchange.com/rooms/41755/ef-hacking –

risposta

9

So che probabilmente non è quello che ti aspetti, ma immagino che almeno ti possa aiutare a non sprecare altro tempo in quella direzione.

La buona notizia è che il problema non è causato dal codice "hacker". In effetti sono stato in grado di riprodurre il problema senza utilizzare quel codice. Ho appena creato una tabella QUOTE_HOUSE contenente uno TestCol, l'ho importata in un nuovo contesto edmx e ho appena eliminato la proprietà generata TestCol dalla classe di entità. Poi ho fatto ereditare dalla classe il DynamicEntity, creato un contesto usando il costruttore predefinito, chiamato context.QUOTE_HOUSE.ToList() e si è verificato con la stessa identica eccezione.

La cattiva notizia è che quello che stai cercando di ottenere non è possibile. EF non usa nient'altro e nientemeno che una riflessione per mappare i membri dello "spazio oggetti". Ad esempio, non offre alcun meccanismo di estensione del tipo come TypeDescriptor o runtime dinamico (non consente nemmeno di proiettare oggetti dinamici). Il più tardi è comprensibile, dal momento che ogni oggetto dinamico può avere proprietà diverse e non esiste qualcosa di dinamico come tipo. Si noti che il trucco con la "rimozione" delle colonne in fase di esecuzione funziona perché ciò che fa è praticamente lo stesso di utilizzare NotMapped nel codice, vale a dire la proprietà esiste realmente, ma viene ignorata da EF.

Se siete interessati perché l'entità è in CSpace ma non in OSpace, la risposta è contenuta in una classe interna denominata OSpaceTypeFactory (all'interno System.Data.Entity.Core.Metadata.Edm namespace) - source. Esiste un metodo chiamato TryCreateStructuralType, che chiama TryCreateMembers e se restituisce false, il tipo non viene aggiunto.TryCreateMembers a turno chiamate TryFindAndCreatePrimitiveProperties, passando l'elenco PropertyInfo estratta utilizzando la riflessione, ei rendimenti successive false se non si può mappare qualsiasiCSpace membro alla proprietà OSpace oggetto, impedendo così efficacemente il tipo da aggiungere alla collezione OSpace tipo.

Spero almeno che soddisfi la tua curiosità :) Ma ancora una volta, "l'aggiunta" di proprietà in fase di runtime all'entità EF è purtroppo un'idea sbagliata.