我正在使用第三方数据库,其中所有文本值都存储为char(n)
。其中一些文本值是主键,而其他文本值只是普通的人类可读文本。对于后者,我希望自动修剪检索到的值。
我知道我可以将Trim
添加到我的所有LINQ to Entities查询中,但这很麻烦,不可靠且不可维护。我想以某种方式配置实体框架以自动修剪从特定列检索的值。
但是,我不知道该怎么做。我正在使用EF的流畅API。到目前为止,我所想到的最接近的事情是使用Trim
方法调用创建其他属性来包装真实属性,但这很麻烦,但仍然不能很好地维护。我也希望在数据库而不是应用程序中进行修剪。
Rowan Miller(微软实体框架项目经理)最近发布了一个使用拦截器的好解决方案。不可否认,这仅适用于EF 6.1+。他的帖子是关于联接中的字符串尾随,但基本上,应用的解决方案会自动地从模型中的所有字符串属性中自动删除尾随字符串,而不会显着影响性能。
原帖博客:Working around trailing blanks issue in string joins
相关代码转发到这里,但我鼓励您阅读他的博客文章。 (如果你使用EF,你应该阅读他的博客)。
using System.Data.Entity.Core.Common.CommandTrees;
using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure.Interception;
using System.Linq;
namespace FixedLengthDemo
{
public class StringTrimmerInterceptor : IDbCommandTreeInterceptor
{
public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
{
if (interceptionContext.OriginalResult.DataSpace == DataSpace.SSpace)
{
var queryCommand = interceptionContext.Result as DbQueryCommandTree;
if (queryCommand != null)
{
var newQuery = queryCommand.Query.Accept(new StringTrimmerQueryVisitor());
interceptionContext.Result = new DbQueryCommandTree(
queryCommand.MetadataWorkspace,
queryCommand.DataSpace,
newQuery);
}
}
}
private class StringTrimmerQueryVisitor : DefaultExpressionVisitor
{
private static readonly string[] _typesToTrim = { "nvarchar", "varchar", "char", "nchar" };
public override DbExpression Visit(DbNewInstanceExpression expression)
{
var arguments = expression.Arguments.Select(a =>
{
var propertyArg = a as DbPropertyExpression;
if (propertyArg != null && _typesToTrim.Contains(propertyArg.Property.TypeUsage.EdmType.Name))
{
return EdmFunctions.Trim(a);
}
return a;
});
return DbExpressionBuilder.New(expression.ResultType, arguments);
}
}
}
}
Rowan继续说道:“现在我们有一个拦截器,我们需要告诉EF使用它。这最好通过基于代码的配置完成。我们可以将下面的类放在与我们的上下文相同的程序集/项目中,EF将选择它。“
using System.Data.Entity;
namespace FixedLengthDemo
{
public class MyConfiguration : DbConfiguration
{
public MyConfiguration()
{
AddInterceptor(new StringTrimmerInterceptor());
}
}
}
在实体上使用带有支持字段的属性而不是自动属性。
在属性设置器中添加“Trim()”,如下所示:
protected string _name;
public String Name
{
get { return this._name; }
set { this._name = (value == null ? value : value.Trim()); }
}
我编写了自己的POCO生成器,它自动执行此操作,但如果您没有这样的选项,ReSharper可以像两次击键一样将自动属性的后备字段添加到自动属性中。只需为字符串执行此操作,您就可以使用“= value;
”在全局(在文件范围内)查找/替换“= value.Trim();
”。
实体框架不提供钩子来改变它组成SQL语句的方式,因此你无法告诉它从数据库中获取和修剪字符串字段。
可以在ObjectContext.ObjectMaterialized
事件中修剪字符串属性,但我认为这会极大地影响性能。此外,需要大量的if-else
或switch
代码来执行特定属性(如您打算这样做)。但是如果你想为几乎所有的属性(例如键除外)做这个,那么值得一试。
否则我会去寻找其他房产。
我有同样的问题。我在DbContext中通过这种简单的方式解决了它:
public partial class MyDbContext : DbContext
{
public override int SaveChanges()
{
foreach (var entity in this.ChangeTracker.Entries())
{
foreach (PropertyEntry property in entity.Properties.ToList().Where(o => !o.Metadata.IsKey()))
TrimFieldValue(property);
}
return base.SaveChanges();
}
private void TrimFieldValue(PropertyEntry property)
{
var metaData = property.Metadata;
var currentValue = property.CurrentValue == null ? null : property.CurrentValue.ToString();
var maxLength = metaData.GetMaxLength();
if (!maxLength.HasValue || currentValue == null) return;
if (currentValue.Length > maxLength.Value)
property.CurrentValue = currentValue.Substring(0, maxLength.Value);
}
}
我使用了Stuart Grassie给出的方法,但它起初没有用,因为列类型只包含“char”,“varchar”等。列实际上是“char(30)”,“varchar(10)”,等一旦我改变了它后面的线就像一个魅力!
来自:if (propertyArg != null && _typesToTrim.Contains(propertyArg.Property.TypeUsage.EdmType.Name))
to:if (propertyArg != null && _typesToTrim.Any(t => propertyArg.Property.TypeUsage.EdmType.Name.Contains(t)))
谢谢斯图尔特!