c# - Entity Framework 6. Truncar automáticamente los campos de cadena que exceden la longitud del campo DB

CorePress2024-01-25  11

En primer lugar, ¿por qué hacer esto? En el sistema de producción, si llegan datos que son demasiado grandes para la base de datos, es posible que desee seguir ejecutando y aceptar la pérdida de datos si los campos no son críticos, y habrá problemas mayores si el registro no se inserta.

Tengo esto funcionando con el siguiente código, pero es feo tener que reflexionar sobre las propiedades privadas del marco .net.

¿Seguramente hay una mejor manera de hacer esto? Vi el siguiente código en otra publicación para cargar los metadatos pero no funciona. Los tipos cargados de esta manera no tienen completadas las longitudes de los campos de la base de datos. Generamos modelo a partir de DB. De esa manera nunca tendremos que hacer ajustes manuales al modelo de entidad nosotros mismos (el primer método de base de datos).

var metaDataWorkspace = ((IObjectContextAdapter)db).ObjectContext.MetadataWorkspace;
var edmTypes = metaDataWorkspace.GetItems<EdmType>(DataSpace.OSpace);

El método con la lógica está en AutoTruncateStringToMaxLength() a continuación.

tiene los siguientes usos:

using System.Data.Entity;
using System.Data.Entity.Validation;
using System.Data.Entity.Core.EntityClient;
using System.Data.Entity.Core.Metadata.Edm;

código que se encuentra en una clase de entidad parcial (por ejemplo, clase pública parcial MyEntities):

public override int SaveChanges()
    {
        try
        {
            this.AutoTruncateStringToMaxLength();
            return base.SaveChanges();
        }
        catch (DbEntityValidationException ex)
        {
            List<string> errorMessages = new List<string>();
            foreach (DbEntityValidationResult validationResult in ex.EntityValidationErrors)
            {
                string entityName = validationResult.Entry.Entity.GetType().Name;
                foreach (DbValidationError error in validationResult.ValidationErrors)
                {
                    errorMessages.Add(entityName + "." + error.PropertyName + ": " + error.ErrorMessage);
                }
            }

            // Join the list to a single string.
            var fullErrorMessage = string.Join("; ", errorMessages);

            // Combine the original exception message with the new one.
            var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);

            // Throw a new DbEntityValidationException with the improved exception message.
            throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
        }
    }

    private void AutoTruncateStringToMaxLength()
    {
        var entries = this?.ChangeTracker?.Entries();
        if (entries == null)
        {
            return;
        }

        //********** EDM type from here does not work. MaxLength properties are not set ***************** //
        //var metaDataWorkspace = ((IObjectContextAdapter)db).ObjectContext.MetadataWorkspace;
        //var edmTypes = metaDataWorkspace.GetItems<EdmType>(DataSpace.OSpace);

        ReadOnlyMetadataCollection<EdmMember> memberMetaDataProperties = null;
        string currentloadedEdmType = null;
        foreach (var entry in entries)
        {
            var internalEntry = entry.GetType().GetProperty("InternalEntry", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(entry);
            var edmType = (EdmType)internalEntry.GetType().GetProperty("EdmEntityType", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).GetValue(internalEntry);

            if (edmType != null)
            {
                if (currentloadedEdmType == null || edmType.Name != currentloadedEdmType)
                {
                    currentloadedEdmType = edmType.Name;
                    //seems slow to load (in debug) so cache just in case there is performance issue
                    memberMetaDataProperties = (ReadOnlyMetadataCollection<EdmMember>)edmType.MetadataProperties.FirstOrDefault(x => x.Name == "Members").Value;
                }

                entry.Entity.GetType().GetProperties().ToList().ForEach(p =>
                {
                    var matchingMemberMetaData = memberMetaDataProperties.FirstOrDefault(x => x.Name == p.Name);
                    if (matchingMemberMetaData != null && matchingMemberMetaData.BuiltInTypeKind.ToString() == "EdmProperty")
                    {
                        var edmProperty = (EdmProperty)matchingMemberMetaData;
                        if (edmProperty.MaxLength.HasValue && edmProperty.TypeName == "String")
                        {
                            string value = (p.GetValue(entry.Entity) ?? "").ToString();
                            if (value.Length > edmProperty.MaxLength.Value)
                            {
                                // oops. Its too Long, so truncate it.
                                p.SetValue(entry.Entity, value.Substring(value.Length - edmProperty.MaxLength.Value));
                            }
                        }
                    }
                });
            }
        }
    }

    

1

He utilizado la siguiente solución con éxito utilizando un método de extensión. medium.com/@paullorica/…

- JFTxJ

7 de julio de 2022 a las 20:21



------------------------------------

EF Core tiene una mejor API de metadatos. por ejemplo

var q = from e in this.Model.GetEntityTypes()
        from p in e.GetProperties()
        where p.ClrType == typeof(string)
        select new { EntityName = e.Name, PropertyName = p.Name, MaxLength = p.GetMaxLength() };



------------------------------------

¡Esto fue muy útil! Ya nos llevaste casi todo el camino hasta allí... la solución principal para evitar el uso de la reflexión es arreglar los metadatos que comentaste. Debería haber estado usando EntityType (en lugar de EDMType) y buscando en CSpace (espacio conceptual/modelo); no el OSpace. Luego pude usar su código como punto de partida para llegar a esto.

Dado que utilizamos varios modelos EF en el mismo proyecto, escribí esto como un método de extensión interno, por lo que se puede llamar desde cualquier DbCon.texto, en lugar de un miembro privado de un DbContext específico. También agregué código para iniciar sesión cada vez que se trunca un campo. El programa "GlobalLogger" Las líneas solo apuntan al registrador que utilizamos; puede eliminarlas o actualizarlas para que funcionen con su propio registrador:

internal static void AutoTruncateStrings(this DbContext dbContext)
        {
            try
            {
                var metaDataWorkspace = ((IObjectContextAdapter)dbContext).ObjectContext.MetadataWorkspace;
                var modelMetadata = metaDataWorkspace.GetItems<EntityType>(DataSpace.CSpace);
                var entries = dbContext?.ChangeTracker?.Entries();
                if (entries == null) return;
                foreach (var entry in entries)
                {
                    if (entry.State != EntityState.Added && entry.State != EntityState.Modified) continue; // Only process added or modified entries.
                    var efEntityName = entry.Entity.GetType().FullName;
                    if (efEntityName.StartsWith("System.Data.Entity.DynamicProxies")) efEntityName = entry.Entity.GetType().BaseType.FullName;
                    foreach (var propertyName in entry.CurrentValues.PropertyNames)
                    {
                        var entityMetadata = modelMetadata.Single(e => e.FullName == efEntityName);
                        var propMetadata = entityMetadata.Properties[propertyName];
                        if (propMetadata.TypeName.ToLowerInvariant() != "string" || propMetadata.MaxLength == null) continue;
                        if (entry.CurrentValues[propertyName]?.ToString().Length > propMetadata.MaxLength)
                        {
                            // Truncate value, and log that it was truncated.
                            string truncatedValue = Common.Left(entry.CurrentValues[propertyName].ToString(), propMetadata.MaxLength.Value);
                            GlobalLogger.Logger.LogWarning($"A string value on an Entity Framework entity had to be truncated because it exceeded its  maximum length." +
                                $"\n\n" +
                                $"Entity: {efEntityName}\n" +
                                $"Entity Key: [{string.Join(", ", entityMetadata.KeyProperties.Select(kp => kp.Name))}] = [{string.Join(", ", entityMetadata.KeyProperties.Select(kp => entry.CurrentValues[kp.Name].ToString()))}]\n" +
                                $"Property: {propertyName}\n" +
                                $"Maximum Length: {propMetadata.MaxLength.Value}\n" +
                                $"Original Length: {entry.CurrentValues[propertyName].ToString().Length}\n" +
                                $"Original Value: {entry.CurrentValues[propertyName]}\n" +
                                $"Truncated Value: {truncatedValue}");
                            entry.CurrentValues[propertyName] = truncatedValue;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                GlobalLogger.Logger.LogError("An unhandled exception occurred while trying to auto-truncate Entity Framework entity string properties.  Automatic truncation will be skipped for the remainder of this SaveChanges() batch.", ex);
            }
        }

4

En EF Core, es posible que pueda utilizar un convertidor de valores.

- Jeremy Lakeman

4 de mayo de 2023 a las 1:11

Gracias. Olvidé mencionar que agregué esto a pesar de que David Browne mencionó anteriormente que EF Core podría ser mejor, porque todavía somos una tienda de .NET Framework y sospecho que muchos otros también lo son. :)

Maximiliano C.

4 de mayo de 2023 a las 8:42

Gracias por el ejemplo de código. No veo dónde está definido _entitiesMissingFromMetadata. ¿Puedes mostrarnos cómo construir esa cadena?

- usuario2800837

Mayo4/2023 a las 17:59

¡Ups! Lo siento, en realidad quise eliminarlo. Lo he eliminado ahora. ¡Me encantaría recibir mi primer voto positivo de tu parte! :)

Maximiliano C.

7 de mayo de 2023 a las 3:27

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare