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 En
I used a slightly different tack, but also utilizing the On*Changed methods. I'm generating partial classes using a stripped down version of the .tt file used by EF. The relevant section is where the properties are generated. The Maximum length is available and can be used to truncate the string.
foreach (EdmProperty property in
entity.Properties.Where(p => p.DeclaringType == entity
&& p.TypeUsage.EdmType is PrimitiveType))
{
/// If this is a string implements its OnChanged method
if (property.TypeUsage.ToString() != "Edm.String") continue;
int maxLength = 0;
if (property.TypeUsage.Facets["MaxLength"].Value == null) continue;
if (!Int32.TryParse(property.TypeUsage.Facets["MaxLength"].Value.ToString(),
out maxLength)) continue;
if (maxLength == 0) continue;
// Implement the On*Changed method
#>
partial void On<#= property.Name#>Changed() {
<#=code.FieldName(property)#> =#=code.FieldName(property)#>.Substring(0,<#= maxLength #>);
}
<#
}
This approach uses the attributes of the object properties, so it works with EF or perhaps other scenarios. If a property has a "StringLength" attribute, it will be truncated.
// Truncate any string that is too long.
var entry = new MyObject(); // Entity Framework object
entry.GetType().GetProperties().ToList().ForEach(p =>
{
foreach (StringLengthAttribute attribute in p.GetCustomAttributes(true)
.Where(a => a is StringLengthAttribute).Cast<StringLengthAttribute>())
{
string value = (p.GetValue(entry) ?? "").ToString();
if (value.Length > attribute.MaximumLength)
{
// oops. Its too Long, so truncate it.
p.SetValue(entry, value.Substring(0, attribute.MaximumLength));
}
}
});
this tested correctly using this example property (due to StringLength)
[Required]
[StringLength(6)] // only 6, for testing
public string Message { get; set; }
(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));
}
}
}
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;
}