Entity Framework 4.0 автоматически обрезает/обрезает строку перед вставкой

Предположим, что у меня есть таблица со столбцом Description, varchar (100). Если попытаться вставить строку с более чем 100 символами, вставка не будет выполнена.

Есть ли способ в Entity Framework автоматически обрезать или обрезать строку, чтобы она вписывалась в столбец, прежде чем вставлять в столбец? В моем сценарии мне действительно все равно, урезана ли строка, я просто хочу, чтобы она вставлена, а не просто сбой и запись в rror.

Поскольку модель уже знает пределы длины, я подумал, что для Entity Framework может быть способ сделать это для меня.

Если это не поддерживается, каков наилучший способ сделать это? Расширить автоматически сгенерированные частичные классы и переопределить методы On * Changed? Я бы предпочел не жестко кодировать ограничения длины, а использовать ограничения длины, уже определенные в модели сущности. Как я могу получить доступ к этому?

Edit

Моим окончательным решением было внедрить метод On * Changed частично автогенерированного объекта.

Я использовал этот метод для получения объекта ObjectContext из экземпляра сущности, а затем использовал метод ниже, чтобы извлечь максимальную длину и усечь строку.

Ответ 1

Это даст вам максимальную длину столбца.

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

Здесь мое однострочное решение

(ссылаясь на одну строку, реализация немного больше)

Я взял код из @elbweb и адаптировал его для своих целей. В моем случае я разбирал файлы EDI, некоторые из которых имели 15 различных уровней иерархии, и я не хотел явно указывать все 15 разных типов - мне нужен один-лайнер, который работал для всех типов сущностей.

Это немного другое, но теперь безболезненно звонить. Это определенно удар по производительности, но это приемлемо для меня. По существу, это внутри вашего класса DbContext, а затем однострочный вызов для вызова вручную (или вы можете автоматически вызвать его, переопределив SaveChanges для его вызова).

Код в вашем 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));
                }
            }
        }
    }
}

Потребление

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

Перед этим кодом SaveChanges вызовет catch в моем примере выше, когда вы попытались вставить строку, которая была слишком большой. После добавления строки TruncateAllStringsOnAllEntitiesToDbSize она отлично работает! Я уверен, что есть некоторые оптимизации, которые могут пойти на это, так что, пожалуйста, критикуйте/внесите свой вклад!: -)

Примечание. Я только пробовал это на EF 6.1.3

Ответ 3

Я воспользовался логикой ответа Ричарда и превратил его в метод обрезания всех строк объекта фреймворка сущности на основе их максимальной длины, если они ограничены.

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

Я использовал несколько иной подход, но также использовал методы On * Changed. Я генерирую частичные классы, используя урезанную версию файла .tt, используемую EF. В соответствующем разделе создаются свойства. Максимальная длина доступна и может использоваться для усечения строки.

 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

Этот подход использует атрибуты свойств объекта, поэтому он работает с EF или, возможно, с другими сценариями. Если свойство имеет атрибут "StringLength", оно будет усечено.

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

это проверено правильно, используя этот пример свойства (из-за StringLength)

[Required]
[StringLength(6)] // only 6, for testing
public string Message { get; set; }