EntityFramework v1 .Net3.5 Sp1 动态查询过滤检索所遇到的问题
最近在封装EntityFramework的时候碰到许多问题,下面就做一个简短的笔记.
泛型推断
例如Save<T>(T o)这样的方法是没有问题的,但是如果参数再包裹一层泛型就不行了,Save<T>(IEnumerable<T> o),所以只能在方法体类判断是否实现了IEnumerable<T>接口,然后分情况处理.关于更多C#3.0中的泛型推断.大体上就是说C#编译器只能推断出明确的一种类型,而不能同时包含多种,比如上面的Save<T>(IEnumerable<T> o)则编译器无法得知参数的具体类型,就需要指定类型.
拓展方法的访问级别
基本上就是一个外部的类,除非在同一程序集里并设定被拓展对象为internal访问级别才能访问,否则protected都访问不到.
封装EntityFremawork操作
这里主要讲讲查询,一般来说Find方法的签名如下
起始元素,页长度,过滤语句,排序语句,最后一个是EntityFramework独有,是否与上下文对象解绑.
在构造ObjectQuery时就需要获得EF的映射关系,用System.Data.Metadata.Edm读取映射信息就可以了,这里我就直接将代码贴出来了.接受一个EntityContainer就可以不断的读取信息了,本身继承一个见到那的缓存类,实际上就是对字典的一个简单封装.
{
public EntitySetBase EntitySetBase { get; set; }
public string TableName { get; set; }
public string TypeName { get; set; }
public string KeyName { get; set; }
}
public class EdmInfoManager : SimpleCache<Type, EdmInfo>
{
public EntityContainer RootContainer { get; private set; }
public EdmInfoManager(EntityContainer ec)
{
RootContainer = ec;
}
public new EdmInfo this[Type T]
{
get
{
if(!ContainsKey(T))
{
EdmInfo edm = new EdmInfo();
edm.EntitySetBase = typeToEntitySetBase(T);
edm.TableName = edm.EntitySetBase.Name;
edm.TypeName = T.Name;
edm.KeyName = entitySetBaseToKeyName(edm.EntitySetBase);
base[T] = edm;
return edm;
}
return base[T];
}
set
{
base[T] = value;
}
}
private EntitySetBase typeToEntitySetBase(Type t)
{
return RootContainer.BaseEntitySets
.Where(bes => bes.ElementType.Name == t.Name)
.FirstOrDefault();
}
private string entitySetBaseToKeyName(EntitySetBase es)
{
return es.ElementType.KeyMembers[0].Name;
}
}
然后就是排序项,由于项目有一些lambda表达式风格的排序,以及普通属性字符串似的排序,所以又做了一个简单封装,IOrderItem<T>结构下有两个实现StringOrderItem与ExpressionOrderItem.主要是StringOrderItem,因为最后都将排序信息保存为字符串了,所以只能以字符串形式排序,这就为后面造成了一些人为的障碍.
过滤项就是封装了一下Expression<Func<T, bool>>,不过提供从字符串构造的方法.最后都是Lambda表达式的形式存储.
最后检索的时候就碰到问题了,ObjectQuery.Where(Expression<Func<T, bool>> exp)是一个拓展方法,返回的不是ObjectQuery,导致了后面无法使用OrderBy(String orderstring).只能使用OrderBy的拓展方法Lambda表达式形式.卡壳了很久,找了一些资料.其中不乏微软的EF拓展库.有个拓展方法能够强制转换为ObjectQuery在链式操作中,但是在我这里一旦强转后面的操作都报错,大概是只支持EFv4吧.
能否将IWhereItem里的Lambda表达式转为ESQL语句呢?.试着重写一个ExpressionVisitor可惜没功夫没到家,表达式的资料到是找到了不少.就是没写出来.
表达式的各种构造 : http://rednaxelafx.javaeye.com/blog/247270
LinqToSQL : 一种Linq到TSQL语句的转换,地址忘记了,代码在此
在stackoverflow的提问 : http://stackoverflow.com/questions/3305224/problems-with-entity-framework-queries
就想把Order转为Lambda表达式形式,但是如何泛型如何设置呢?Expression<Func<T, object>>在面对Expression<Func<T, int>>时会自动有个转换过程,详见Lambda传参自动包装.所以无法直接使用Expression<Func<T, object>>类型传递,在动态构造Linq过滤,排序时发现了一个解决方法,使用IOrderedQueryable包装过滤项,继续搜索,终于找到了使用泛型反射出类型,然后动态构造Lambda表达式(且指定类型)的解决方法了.
{
var item = Expression.Parameter(typeof(T), "item");
var propertyValue = Expression.PropertyOrField(item, columnName);
var propertyLambda = Expression.Lambda(propertyValue, item);
// item => item.{columnName}
var sourceExpression = Expression.Parameter(typeof(IEnumerable<T>), "source");
string methodName;
Expression inputExpression;
if (started)
{
methodName = isAscending ? "ThenBy" : "ThenByDescending";
inputExpression = Expression.Convert(sourceExpression, typeof(IOrderedEnumerable<T>));
// ThenBy requires input to be IOrderedEnumerable<T>
}
else
{
methodName = isAscending ? "OrderBy" : "OrderByDescending";
inputExpression = sourceExpression;
}
var sortTypeParameters = new Type[] { typeof(T), propertyValue.Type };
var sortExpression = Expression.Call(typeof(Enumerable), methodName, sortTypeParameters, inputExpression, propertyLambda);
var sortLambda = Expression.Lambda<Func<IEnumerable<T>, IOrderedEnumerable<T>>>(sortExpression, sourceExpression);
// source => Enumerable.OrderBy<T, TKey>(source, item => item.{columnName})
return sortLambda.Compile()(source);
}
public static IOrderedEnumerable<T> OrderBy<T>(this IEnumerable<T> source, string columnName)
{
return SortEngine(source, columnName, true, false);
}
public static IOrderedEnumerable<T> OrderByDescending<T>(this IEnumerable<T> source, string columnName)
{
return SortEngine(source, columnName, false, false);
}
public static IOrderedEnumerable<T> ThenBy<T>(this IOrderedEnumerable<T> source, string columnName)
{
return SortEngine(source, columnName, true, true);
}
public static IOrderedEnumerable<T> ThenByDescending<T>(this IOrderedEnumerable<T> source, string columnName)
{
return SortEngine(source, columnName, false, true);
}
最后Find方法全貌
{
var context = getInstence();
var edminfo = EdmMgr[typeof (T)];
ObjectQuery<T> query = context.CreateQuery<T>("[" + edminfo.TableName + "]");
query = MemerVisitor.IncludeMembers.Aggregate(query, (current, str) => current.Include(str));
orders = orders ?? OrderItemFactry.Get<T>(edminfo.KeyName, true);
List<T> result;
result = whereitem == null || whereitem.Body == null
? start == -1
? query.Skip(orders.OrderString, "0").ToList()
: query.Skip(orders.OrderString, start.ToString()).Take(len).ToList()
: start == -1
? query.Skip(orders.OrderString, "0").Where(whereitem.Body).ToList()
: query.Skip(orders.OrderString, "0").Where(whereitem.Body).OrderBy(orders.Property).Skip(start).Take(len).ToList();
if (isDetach && MemerVisitor.IncludeMembers.Count == 0)
result.ForEach(x =>
{
try
{
context.Detach(x);
}
catch (InvalidOperationException)
{
}
});
MemerVisitor.IncludeMembers.Clear();
return result;
}