首先,为什么要这样做?在生产系统中,如果传入的数据对于数据库来说太大了,如果字段不重要,您可能希望继续运行并接受数据丢失,如果不插入记录,则会出现更大的问题。
我用下面的代码来工作,但是不得不反映私有的.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));
}
}
}
});
}
}
}
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() };
这超级有帮助!您已经完成了大部分工作……避免使用反射的主要修复方法是修复您注释掉的元数据内容。您应该一直在使用 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);
}
}