Db 操作に EF 5 Code First を使用する MVC アプリケーションがあります。また、EF には Generic Repository パターンがあります。
クエリをDBレイヤーに送信する前に、動的linqクエリを作成して別のクエリと組み合わせるなどの操作を行います。
Expression<Func<AssetItemInfo, bool>> dynamicFilter = DynamicLinqFactory<AssetItemInfo>.GetFilter(cmd.sSearch, searchColumns);
Expression<Func<AssetItemInfo, bool>> deleteFilter = c => c.CurrentStatus != AssetStatus.Deleted;
var body = Expression.AndAlso(dynamicFilter.Body, deleteFilter.Body);
Expression<Func<AssetItemInfo, bool>> filter = Expression.Lambda<Func<AssetItemInfo, bool>>(body, dynamicFilter.Parameters[0]);
DBレイヤーでの私のGetメソッドは以下のようなものです。
public virtual PaginatedList<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "",
int? page = null,
int? take = null
) {
IQueryable<TEntity> query = dbSet;
if(filter != null) {
query = query.Where(filter);
}
if(includeProperties != null) {
foreach(var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) {
query = query.Include(includeProperty);
}
}
if(orderBy != null) {
return orderBy(query).ToList().ToPaginatedList(page.Value, take.Value);
}
else {
if(!page.HasValue) page = 0;
if(!take.HasValue) take = 0;
return query.ToList().ToPaginatedList(page.Value, take.Value);
}
}
ご覧のとおり、Expression<Func<TEntity, bool>>
type のフィルター パラメーターがあります。したがって、手動フィルターを作成すると、非常にうまく機能します。しかし、私は前進して、特定のモデルのすべてのプロパティで動的フィルター検索キーワードを作成したいと考えています。この目的のために、以下の方法を使用します。
public class DynamicLinqFactory<TEntity> where TEntity : class, IDomainEntity {
public static Expression<Func<TEntity, bool>> GetFilter(string filter, IEnumerable<string> filterTargets = null) {
ParameterExpression c = Expression.Parameter(typeof(TEntity), "c");
Type[] ContainsTypes = new Type[1];
ContainsTypes[0] = typeof(string);
MethodInfo myContainsInfo = typeof(string).GetMethod("Contains", ContainsTypes);
if(filterTargets == null) {
filterTargets = typeof(TEntity).GetProperties().Where(p => !p.GetMethod.IsVirtual && !p.Name.EndsWith("ID")).Select(p=>p.Name).ToList();
}
List<Expression> myFilterExpressions =
filterTargets.Select<string, Expression>(s =>
Expression.Call(
Expression.Call(
Expression.Property(c, typeof(TEntity).GetProperty(s)),
"ToString",
null,
null
),
myContainsInfo,
Expression.Constant(filter)
)
).ToList();
Expression OrExpression = null;
foreach(Expression myFilterExpression in myFilterExpressions) {
if(OrExpression == null) {
OrExpression = myFilterExpression;
}
else {
OrExpression = Expression.Or(myFilterExpression, OrExpression);
}
}
Expression<Func<TEntity, bool>> predicate = Expression.Lambda<Func<TEntity, bool>>(OrExpression, c);
return predicate;
}
}
上記のメソッドは、名前に「ID」サフィックスが含まれる仮想メンバーとメンバーを排除し、以下でサンプリングされた動的式を生成します。
.Lambda #Lambda1<System.Func`2[Radore.Models.Asset.AssetItem,System.Boolean]>(Radore.Models.Asset.AssetItem $c) {
.Call (.Call ($c.UpdateDate).ToString()
).Contains("tes") | .Call (.Call ($c.UpdatedBy).ToString()).Contains("tes") | .Call (.Call ($c.ServiceTag).ToString()).Contains("tes")
| .Call (.Call ($c.Price).ToString()).Contains("tes") | .Call (.Call ($c.CurrentStatus).ToString()).Contains("tes") | .Call (.Call ($c.CreatedDate).ToString()
).Contains("tes") | .Call (.Call ($c.CreatedBy).ToString()).Contains("tes") | .Call (.Call ($c.Name).ToString()).Contains("tes")
&& (System.Int32)$x.CurrentStatus != 3
}
しかし、アプリケーションを実行しようとすると、以下のエラーが発生しました。
LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression.
ToString() メソッドを削除し、以下のようにクエリを簡素化しました。
.Lambda #Lambda1<System.Func`2[Radore.Models.Asset.AssetItem,System.Boolean]>(Radore.Models.Asset.AssetItem $c) {
.Call (.Call ($c.Name).ToString()).Contains("tes") && (System.Int32)$c.CurrentStatus != 3
}
しかし、今回c
はLinqにバインドされていないというエラーが表示されました。
動的クエリを正常に作成する方法を知っていますか?