2011-02-22 12 views
11

Supongamos que tengo una tabla con la columna Descripción, varchar (100). Si intenta insertar una cadena con más de 100 caracteres, la inserción fallará.Entidad Framework 4.0 Truncar automáticamente/Trim cadena antes Insertar

¿Hay alguna manera en Entity Framework de truncar o recortar automáticamente la cadena para que quepa en la columna antes de insertarla en la columna? En mi caso, realmente no me importa si la cadena está truncada, solo quiero que se inserte en lugar de solo fallar y registrar el error.

Dado que el modelo ya conoce los límites de longitud, estaba pensando que podría haber una forma de que Entity Framework hiciera esto por mí.

Si esto no es compatible, ¿cuál es la mejor manera de hacerlo? ¿Extiende las clases parciales generadas automáticamente y anula los métodos On * Changed? Preferiría no codificar los límites de longitud, sino usar los límites de longitud ya definidos en el modelo de entidad. ¿Cómo podría tener acceso a esto?

Editar

Mi solución final era poner en práctica el método * El parcial ha cambiado de la entidad autogenerada.

Usé this method para obtener el ObjectContext de la instancia de entidad, y luego usé el siguiente método para extraer la longitud máxima y truncar la cadena.

Respuesta

7

Esto le dará la longitud máxima de una columna ..

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

he usado una perspectiva un poco diferente, pero también la utilización de la A * métodos cambiado. Estoy generando clases parciales usando una versión reducida del archivo .tt utilizado por EF. La sección relevante es donde se generan las propiedades. La longitud máxima está disponible y se puede usar para truncar la cadena.

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

que tomaron parte de la lógica de la respuesta de Richard y lo convirtieron en un método para truncar todas las cadenas de un objeto marco de la entidad en función de su longitud máxima, si son limitados.

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

Aquí está mi de una línea de soluciones

(invocando es una línea, la aplicación es un poco más)

Tomé el código de @elbweb y lo adaptó para mis propósitos. En mi caso, estaba analizando archivos EDI, algunos de los cuales tenían 15 niveles diferentes para la jerarquía y no quería especificar explícitamente los 15 tipos diferentes. Quería una línea única que funcionara para todos los tipos de entidades.

Es un poco diferente, pero ahora es fácil de llamar. Definitivamente hay un golpe de rendimiento en esto, pero es aceptable para mí. Esencialmente coloque esto dentro de su clase DbContext y luego es una línea única para llamar manualmente (o puede llamarlo automáticamente anulando SaveChanges para invocarlo).

Código

en su 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; 
} 

Antes de este código, el SaveChanges daría lugar a la captura en el ejemplo anterior cuando intentó insertar una cadena que era demasiado grande. Después de agregar la línea TruncateAllStringsOnAllEntitiesToDbSize, ¡funciona genial ahora! Estoy seguro de que hay algunas optimizaciones que pueden entrar en esto, así que por favor critique/contribuya! :-)

Nota: Sólo he intentado esto en EF 6.1.3

+0

Funciona, pero curiosamente, sólo para algunas columnas. Parece que EF no puede recuperar la longitud de la columna de columnas arbitrarias en mi caso. –

+0

@ReuelRibeiro Si puede crear pasos de reproducción y ponerlo en una nueva pregunta, con mucho gusto echaré un vistazo para ver si puedo solucionarlo por usted. No he tenido problemas con este código, aunque recientemente dejé de usarlo ahora que Entity Framework Extensions tiene esta funcionalidad incorporada y la usamos ahora. – Jaxidian

Cuestiones relacionadas