2011-02-22 13 views
11

Supponiamo di avere una tabella con la colonna Descrizione, varchar (100). Se provi ad inserire una stringa con più di 100 caratteri, l'inserimento fallirà.Entity Framework 4.0 Troncamento automatico/Trim stringa prima dell'inserimento

Esiste un modo in Entity Framework per troncare o tagliare automaticamente la stringa per adattarla alla colonna prima di inserirla nella colonna? Nel mio scenario, non mi interessa davvero se la stringa è troncata, voglio solo che sia inserita piuttosto che fallire e loggare il rror.

Poiché il modello conosce già i limiti di lunghezza, stavo pensando che per Entity Framework potrebbe esserci un modo per farlo.

Se questo non è supportato, qual è il modo migliore per farlo? Estendi le classi parziali generate automaticamente e sovrascrivi i metodi On * Changed? Preferirei non codificare i limiti di lunghezza, ma piuttosto usare i limiti di lunghezza già definiti nel modello di entità. Come posso avere accesso a questo?

Modifica

La mia soluzione finale era quello di implementare l'On * Cambiato il metodo parziale dell'entità generato automaticamente.

Ho utilizzato this method di ottenere ObjectContext dall'istanza dell'entità e quindi ho utilizzato il metodo seguente per estrarre la lunghezza massima e troncare la stringa.

risposta

7

Questo vi darà la lunghezza massima di una colonna ..

public int? GetColumnMaxLength(ObjectContext context, string entityTypeName, string columnName) 
    { 
     int? result = null; 

     Type entType = Type.GetType(entityTypeName); 
     var q = from meta in context.MetadataWorkspace.GetItems(DataSpace.CSpace) 
          .Where(m => m.BuiltInTypeKind == BuiltInTypeKind.EntityType) 
       from p in (meta as EntityType).Properties 
       .Where(p => p.Name == columnName 
          && p.TypeUsage.EdmType.Name == "String") 
       select p; 

     var queryResult = q.Where(p => 
     { 
      bool match = p.DeclaringType.Name == entityTypeName; 
      if (!match && entType != null) 
      { 
       //Is a fully qualified name.... 
       match = entType.Name == p.DeclaringType.Name; 
      } 

      return match; 

     }).Select(sel => sel.TypeUsage.Facets["MaxLength"].Value); 
     if (queryResult.Any()) 
     { 
      result = Convert.ToInt32(queryResult.First()); 
     } 

     return result; 
    } 
1

ho usato una tattica leggermente diversa, ma anche utilizzando la On * metodi cambiato. Sto generando classi parziali usando una versione ridotta del file .tt usato da EF. La sezione pertinente è dove vengono generate le proprietà. La lunghezza massima è disponibile e può essere utilizzata per troncare la stringa.

foreach (EdmProperty property in 
     entity.Properties.Where(p => p.DeclaringType == entity 
     && p.TypeUsage.EdmType is PrimitiveType)) 
{ 

     /// If this is a string implements its OnChanged method 
     if (property.TypeUsage.ToString() != "Edm.String") continue; 

     int maxLength = 0; 

     if (property.TypeUsage.Facets["MaxLength"].Value == null) continue; 

     if (!Int32.TryParse(property.TypeUsage.Facets["MaxLength"].Value.ToString(), 
      out maxLength)) continue; 

     if (maxLength == 0) continue; 
     // Implement the On*Changed method 

     #> 
     partial void On<#= property.Name#>Changed() { 
      <#=code.FieldName(property)#> =#=code.FieldName(property)#>.Substring(0,<#= maxLength #>); 

     } 
     <# 
    } 
2

Ho preso alcune della logica dalla risposta di Richard e lo ha trasformato in un metodo per troncare tutte le stringhe di un oggetto Entity Framework in base alla loro lunghezza massima, se sono limitate.

public static void TruncateStringsInEFObject<T>(List<T> entityObjects, ObjectContext context) 
{ 
    var stringMaxLengthsFromEdmx = context.MetadataWorkspace.GetItems(DataSpace.CSpace) 
     .Where(m => m.BuiltInTypeKind == BuiltInTypeKind.EntityType) 
     .SelectMany(meta => (meta as EntityType).Properties 
      .Where(p => p.TypeUsage.EdmType.Name == "String" 
         && p.DeclaringType.Name == typeof(T).Name)) 
     .Select(d => new {MaxLength = d.TypeUsage.Facets["MaxLength"].Value, d.Name}) 
     .Where(d => d.MaxLength is int) 
     .Select(d => new {d.Name, MaxLength = Convert.ToInt32(d.MaxLength)}) 
     .ToList(); 

    foreach (var maxLengthString in stringMaxLengthsFromEdmx) 
    { 
     var prop = typeof(T).GetProperty(maxLengthString.Name); 
     if (prop == null) continue; 

     foreach (var entityObject in entityObjects) 
     { 
      var currentValue = prop.GetValue(entityObject); 
      var propAsString = currentValue as string; 
      if (propAsString != null && propAsString.Length > maxLengthString.MaxLength) 
      { 
       prop.SetValue(entityObject, propAsString.Substring(0, maxLengthString.MaxLength)); 
      } 
     } 
    } 
4

Ecco il mio One Line-Solution

(invocando è una linea, l'implementazione è un po 'più)

ho preso il codice da @elbweb e adattato per i miei scopi. Nel mio caso stavo analizzando i file EDI, alcuni dei quali avevano 15 diversi livelli nella gerarchia e non volevo specificare esplicitamente tutti i 15 diversi tipi: volevo un one-liner che funzionasse per tutti i tipi di entità.

È un po 'diverso ma ora è indolore chiamare. C'è sicuramente un successo in questo, ma è accettabile per me. In sostanza, inserisci questo all'interno della tua classe DbContext e poi è un one-liner per chiamare manualmente (o puoi chiamarlo automaticamente sovrascrivendo SaveChanges per invocarlo).

codice nel tuo DbContext:

public class MyContext : DbContext 
{ 

    ... 

    public void TruncateAllStringsOnAllEntitiesToDbSize() 
    { 
     var objectContext = ((IObjectContextAdapter) this).ObjectContext; 

     var stringMaxLengthsFromEdmx = 
       objectContext.MetadataWorkspace 
          .GetItems(DataSpace.CSpace) 
          .Where(m => m.BuiltInTypeKind == BuiltInTypeKind.EntityType) 
          .SelectMany(meta => ((EntityType) meta).Properties 
          .Where(p => p.TypeUsage.EdmType.Name == "String")) 
          .Select(d => new 
              { 
               MaxLength = d.TypeUsage.Facets["MaxLength"].Value, 
               PropName = d.Name, 
               EntityName = d.DeclaringType.Name 
              }) 
          .Where(d => d.MaxLength is int) 
          .Select(d => new {d.PropName, d.EntityName, MaxLength = Convert.ToInt32(d.MaxLength)}) 
          .ToList(); 

     var pendingEntities = ChangeTracker.Entries().Where(e => e.State == EntityState.Added || e.State == EntityState.Modified).Select(x => x.Entity).ToList(); 
     foreach (var entityObject in pendingEntities) 
     { 
      var relevantFields = stringMaxLengthsFromEdmx.Where(d => d.EntityName == entityObject.GetType().Name).ToList(); 

      foreach (var maxLengthString in relevantFields) 
      { 
       var prop = entityObject.GetType().GetProperty(maxLengthString.PropName); 
       if (prop == null) continue; 

       var currentValue = prop.GetValue(entityObject); 
       var propAsString = currentValue as string; 
       if (propAsString != null && propAsString.Length > maxLengthString.MaxLength) 
       { 
        prop.SetValue(entityObject, propAsString.Substring(0, maxLengthString.MaxLength)); 
       } 
      } 
     } 
    } 
} 

Consumo

try 
{ 
    innerContext.TruncateAllStringsOnAllEntitiesToDbSize(); 
    innerContext.SaveChanges(); 
} 
catch (DbEntityValidationException e) 
{ 
    foreach (var err in e.EntityValidationErrors) 
    { 
     log.Write($"Entity Validation Errors: {string.Join("\r\n", err.ValidationErrors.Select(v => v.PropertyName + "-" + v.ErrorMessage).ToArray())}"); 
    } 
    throw; 
} 

Prima di questo codice, il SaveChanges innescherebbe il fermo nel mio esempio di cui sopra quando si è tentato di inserire una stringa che era troppo grande. Dopo aver aggiunto la linea TruncateAllStringsOnAllEntitiesToDbSize, ora funziona alla grande! Sono sicuro che ci sono alcune ottimizzazioni che possono andare in questo, quindi per favore critica/contribuisci! :-)

Nota: ho provato solo questo su EF 6.1.3

+0

Funziona, ma stranamente, solo per alcune colonne. Sembra che EF non riesca a recuperare la lunghezza della colonna da colonne arbitrarie nel mio caso. –

+0

@ReuelRibeiro Se riesci a creare passi di riproduzione e a metterlo in una nuova domanda, sarei lieto di dare un'occhiata per vedere se posso aggiustarlo per te. Non ho avuto problemi con questo codice anche se recentemente ho smesso di usarlo ora che Entity Framework Extensions ha questa funzionalità integrata e la usiamo ora. – Jaxidian

Problemi correlati