Entity Framework 6.自动截断超过数据库字段长度的字符串字段

问题描述 投票:0回答:2

首先,为什么要这样做?在生产系统中,如果传入的数据对于数据库来说太大了,如果字段不重要,您可能希望继续运行并接受数据丢失,如果不插入记录,则会出现更大的问题。

我用下面的代码来工作,但是不得不反映私有的.net框架属性是很难看的。

当然有更好的方法来做到这一点? 我在另一篇文章中看到了以下用于加载元数据的代码,但它不起作用。以这种方式加载的类型没有填充数据库字段长度。我们从数据库生成模型。这样我们就不必自己手动调整实体模型(数据库优先方法)。

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

具有逻辑的方法在下面的 AutoTruncateStringToMaxLength() 中。

有以下用途:

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

位于部分实体类中的代码(例如公共部分类 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));
                            }
                        }
                    }
                });
            }
        }
    }

    
c# entity-framework
2个回答
1
投票

EF Core 有更好的元数据 API。 EG

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() };

0
投票

这超级有帮助!您已经完成了大部分工作……避免使用反射的主要修复方法是修复您注释掉的元数据内容。您应该一直在使用 EntityType(而不是 EDMType),并在 CSpace(概念/模型空间)中搜索;不是O空间。然后我能够使用您的代码作为起点来实现这一点。我们在某些地方访问多个模型,所以这就是为什么这个方法是内部的,而不是私有的——所以它可以从两个 dbContext 类访问。我也添加了用于记录截断的代码。 “GlobalLogger”行仅指向our 记录器。您可以自由删除它们,或更新它们以使用您的 own 记录器:

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);
            }
        }
© www.soinside.com 2019 - 2024. All rights reserved.