Entity Framework 4.0 Automatically Truncate/Trim String Before Insert

删除回忆录丶 提交于 2019-12-18 12:17:09

问题


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.


回答1:


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



回答2:


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




回答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));
            }
        }
    }



回答4:


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 #>);

        } 
        <#
    } 



回答5:


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


来源:https://stackoverflow.com/questions/5081109/entity-framework-4-0-automatically-truncate-trim-string-before-insert

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!