假设我有一个包含Description Description,varchar(100)列的表。如果尝试插入超过100个字符的字符串,插入将失败。
在插入列之前,Entity Framework中是否有一种方法可以自动截断或修剪字符串以适应列?在我的场景中,我真的不在乎字符串是否被截断,我只是想插入它而不是仅仅失败并记录rror。
由于模型已经知道了长度限制,我认为Entity Framework可能有办法为我做这个。
如果不支持,最好的方法是什么?扩展自动生成的部分类并覆盖On * Changed方法?我宁愿不对长度限制进行硬编码,而是使用已在实体模型中定义的长度限制。我怎么能访问这个?
修改
我的最终解决方案是实现自动生成实体的On * Changed部分方法。
我使用this method从实体实例获取ObjectContext,然后使用下面的方法提取最大长度,并截断字符串。
答案 0 :(得分:8)
这将为您提供列的最大长度..
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;
}
答案 1 :(得分:6)
(调用它是一行,实现更多一点)
我从@elbweb获取代码并根据我的目的进行调整。在我的情况下,我正在解析EDI文件,其中一些文件有15个不同级别的层次结构,我不想明确指定所有15种不同的类型 - 我想要一个适用于所有实体类型的单行程序。
它有点不同但现在很难打电话。这肯定会受到影响,但对我来说这是可以接受的。基本上将它放在你的DbContext类中,然后它是一个单行程来手动调用(或者你可以通过重写SaveChanges来调用它来自动调用它。)
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上试过这个
答案 2 :(得分:2)
我从理查德的答案中获取了一些逻辑,并将其转换为一种方法,根据实体框架对象的最大长度截断它们的所有字符串,如果它们有限的话。
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));
}
}
}
答案 3 :(得分:1)
我使用了稍微不同的方法,但也使用了On * Changed方法。我正在使用EF使用的.tt文件的精简版生成部分类。相关部分是生成属性的位置。最大长度可用,可用于截断字符串。
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 #>);
}
<#
}
答案 4 :(得分:0)
此方法使用对象属性的属性,因此可用于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; }