Suppose I have a table with the column Description, varchar(100). If try to insert a string with more than 100 characters, the insert will fail.
Is there a way in Entity Framework to automatically truncate or trim the string to fit into the column before inserting into the column? In my scenario, I really don't care whether the string is truncated, I just want it inserted rather than just failing and logging the rror.
Since the model already knows the length limits, I was thinking there might be a way for Entity Framework to do this for me.
If this is not supported, what is the best way to do this? Extend the auto-generated partial classes and override the On*Changed methods? I would prefer not to hard-code the length limits, but rather use the length limits already defined in the entity model. How could I get access to this?
Edit
My final solution was to implement the On*Changed partial method of the autogenerated entity.
I used this method of getting the ObjectContext from the entity instance, and then used the below method to extract the max length, and truncate the string.
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;
}
(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).
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));
}
}
}
}
}
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
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));
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With