Entity Framework 4.0 Automatically Truncate/Trim String Before Insert
Here's my One-Line Solution
(invoking it is one line, the implementation is a little more)
I took the code from @elbweb and adapted it for my purposes. In my case I was parsing EDI files, some of which had 15 different levels to the hierarchy and I didn't want to explicitly specify all 15 different types - I wanted a one-liner that worked for all entity types.
It's a bit different but it's now painless to call. There is definitely a performance hit on this but it's acceptable for me. Essentially put this inside of your DbContext class and then it's a one-liner to manually call (or you can automatically call it by overriding SaveChanges to invoke it).
Code in your 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));
}
}
}
}
}
Consumption
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;
}
Before this code, the SaveChanges
would trigger the catch in my example above when you tried inserting a string that was too large. After adding the TruncateAllStringsOnAllEntitiesToDbSize
line, it works great now! I'm sure there are some optimizations that can go into this, so do please critique/contribute! :-)
Note: I have only tried this on EF 6.1.3
This will give you the max length of a column..
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;
}
I took some of the logic from Richard's answer and turned it into a method to truncate all strings of an entity framework object based on their max length, if they're limited.
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));
}
}
}