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