自定义的IQueryable&LT; T&GT; [英] Customizing IQueryable<T>
问题描述
我想定制我的应用程序的实体,使他们有一个属性引用该加载它们的DataContext的。
我觉得最好的办法就是以某种方式创建一个实现IQueryable的一类,并设置实体的DataContext在它的GetEnumerator方法属性。
我的问题是,我该如何使用所用的LINQ提供程序和Ex pression在我执行的IQueryable到SQL,这样我就不必执行他们自己?
BTW:对于我的情况下,有另一种方式
?看看下面code:
公共部分类产品:IEntityBase
{
公共产品()
{
_DataContext =新SampleDataContext();
}
专用长_id;
[列(存储=_ ID,自动同步= AutoSync.OnInsert,的DbType =BigInt有NOT NULL认同,IsPrimaryKey = TRUE,IsDbGenerated =真)
众长标识
{
{返回_id; }
集合{_id = value的; }
}
私人字符串_Name;
[列(存储=_姓名,的DbType =为nvarchar(max)NOT NULL,CanBeNull = FALSE
公共字符串名称
{
{返回_Name; }
集合{_Name =价值; }
}
私人SampleDataContext _DataContext;
//这是属性扩展产品类以及正在返回这个类应设置
//通过IQueryable的&LT; T&GT; .GetEnumerator()
公共SampleDataContext的DataContext
{
{返回_Name; }
集合{_Name =价值; }
}
公共MyQueryable&LT;产品&GT;的GetProducts()
{
MyQueryable&LT;产品&GT;结果=从p在context.Products
其中{一些条件1}
选择磷;
result.DataContext = _DataContext;
返回结果;
}
公共无效的someMethod()
{
//这个查询将不会实际设置DataCotnext属性。
//而生成的sql查询是这样的:
// SELECT * FROM产品WHERE {若干条件1}和{若干条件2}
变种产品=的GetProducts(),其中({一些条件2})。
//现在到GetEnumerator()被调用的产品DataContext属性
//将被设置。
的foreach(在产品VAR项)
{
item.Name =测试名称;
item.DataContext.SubmitChanges();
}
}
}
公共MyQueryable&LT; T&GT;:IQueryable的&LT; T&GT;
其中T:类,IEntityBase
{
//
// IQueryable的执行情况这是我的问题
//
公众的IEnumerator&LT; T&GT;的GetEnumerator()
{
的foreach()(VAR的Provider.GetEnumerator&LT项目; T&GT)
{
item.DataContext = this.DataContext;
产生收益的项目;
}
}
公共SampleDataContext的DataContext {获得;组; }
}
公共接口IEntityBase
{
SampleDataContext的DataContext {获得;组; };
}
更新
我发现自己的答案。这是示例code,以显示我是如何做到这一点。
公共MyQueryable&LT; T,TContext计算值:IQueryable的&LT; T&GT;
其中T:类,IEntityBase
其中,TContext:DataContext的,新的()
{
公共MyQueryable&LT; T&GT;(TContext背景下,IQueryable的&LT; T&GT; baseIQueryable)
{
如果(baseIQueryable == NULL)
抛出新ArgumentNullException(baseIQueryable);
this.Provider = baseIQueryable.Provider;
this.Ex pression = baseIQueryable.Ex pression;
this.DataContext =背景;
}
公众的IEnumerator&LT; T&GT;的GetEnumerator()
{
VAR枚举= Provider.Execute&LT; IEnumerable的&LT; T&GT;&GT;(出pression);
的foreach(在枚举VAR项)
{
item.DataContext = this.DataContext?新TContext();
产生收益的项目;
}
}
IEnumerator的IEnumerable.GetEnumerator()
{
VAR枚举= Provider.Execute&LT; IEnumerable的&GT;(出pression);
的foreach(在枚举VAR项)
{
((IEntityBase&LT; TContext&GT;)项).DataContext = this.DataContext;
产生收益的项目;
}
}
//
//其他实现...
//
公共SampleDataContext的DataContext {获得;组; }
}
公共部分类产品:IEntityBase
{
公共MyQueryable&LT;产品&GT;的GetProducts()
{
VAR的结果=从p在context.Products
其中{一些条件1}
选择磷;
返回新MyQueryable&LT; typeof运算(产品)的DataContext&GT;(this.DataContext,结果);
}
}
我想不出一个简单的方法来做到这一点。你不能注入到LINQ到SQL管道的中间不破坏组合性。要做到这一点,最简单的方法是为通过LINQ到对象的最后一个步骤:
公开的IEnumerable&LT;产品&GT;的GetProducts()
{
IQueryable的&LT;产品&GT;结果=从p在context.Products
其中{一些条件1}
选择磷;
返回result.AsEnumerable()选择(X =&GT; {
x.SomeProp =背景;
返回X;
});
}
但是请注意,这将中断组合性 - 所有下游LINQ到对象
既然你有一个共同的基类/接口,为您的实体,这可以备选地裹在一个扩展方法很类似的行为(但更好地重复使用):
返回result.AssociateWith(上下文);
的东西,如:
公共静态的IEnumerable&LT; T&GT; AssociateWith&LT; T&GT;(
这IEnumerable的&LT; T&GT;资源,
DataContext的情况下)
其中T:IEntityBase
{
的foreach(源牛逼项)
{
item.DataContext =背景;
产生收益的项目;
}
}
I'm trying to customize entities of my application to make them have a property referencing the DataContext that loaded them.
I think the best way is to somehow create a class that implements the IQueryable and set the entity datacontext property in its GetEnumerator method.
My question is, how can I use the Provider and Expression used by Linq to SQL in my implementation of IQueryable so that I don't have to implement them myself?
BTW: For my scenario, is there another way?
Take a look at the following code:
public partial class Product: IEntityBase
{
public Product()
{
_DataContext = new SampleDataContext();
}
private long _Id;
[Column(Storage="_Id", AutoSync=AutoSync.OnInsert, DbType="BigInt NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
public long Id
{
get{ return _Id; }
set{ _Id = value; }
}
private string _Name;
[Column(Storage="_Name", DbType="NVarChar(MAX) NOT NULL", CanBeNull=false
public string Name
{
get{ return _Name; }
set{ _Name = value; }
}
private SampleDataContext _DataContext;
//This is the property extending the Product class and should be set when this class is being returned
//by IQueryable<T>.GetEnumerator()
public SampleDataContext DataContext
{
get{ return _Name; }
set{ _Name = value; }
}
public MyQueryable<Product> GetProducts()
{
MyQueryable<Product> result = from p in context.Products
where {Some Conditions 1}
select p;
result.DataContext = _DataContext;
return result;
}
public void SomeMethod()
{
//This query will not actually set the DataCotnext property.
//And the generated sql query is something like:
//SELECT * FROM Products WHERE {Some Conditions 1} AND {Some Conditions 2}
var products = GetProducts().Where( {Some Conditions 2} );
//Now that the GetEnumerator() is called the DataContext property of the products
//will be set.
foreach( var item in products )
{
item.Name = "Test Name";
item.DataContext.SubmitChanges();
}
}
}
public MyQueryable<T>: IQueryable<T>
where T: class, IEntityBase
{
//
//Implementation of IQueryable which is my question
//
public IEnumerator<T> GetEnumerator()
{
foreach( var item in Provider.GetEnumerator<T>() )
{
item.DataContext = this.DataContext;
yield return item;
}
}
public SampleDataContext DataContext{ get; set; }
}
public interface IEntityBase
{
SampleDataContext DataContext{ get; set; };
}
UPDATE
I found the answer myself. Here it is the sample code to show how I did that.
public MyQueryable<T, TContext>: IQueryable<T>
where T: class, IEntityBase
where TContext: DataContext, new()
{
public MyQueryable<T>(TContext context, IQueryable<T> baseIQueryable)
{
if( baseIQueryable == null )
throw new ArgumentNullException("baseIQueryable");
this.Provider = baseIQueryable.Provider;
this.Expression = baseIQueryable.Expression;
this.DataContext = context;
}
public IEnumerator<T> GetEnumerator()
{
var enumerator = Provider.Execute<IEnumerable<T>>(Expression);
foreach( var item in enumerator )
{
item.DataContext = this.DataContext ?? new TContext();
yield return item;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
var enumerator = Provider.Execute<IEnumerable>(Expression);
foreach( var item in enumerator )
{
((IEntityBase<TContext>)item).DataContext = this.DataContext;
yield return item;
}
}
//
//Other implementations...
//
public SampleDataContext DataContext{ get; set; }
}
public partial class Product: IEntityBase
{
public MyQueryable<Product> GetProducts()
{
var result = from p in context.Products
where {Some Conditions 1}
select p;
return new MyQueryable<typeof(Product), DataContext>(this.DataContext, result);
}
}
I can't think of an easy way to do that. You can't inject into the middle of the LINQ-to-SQL pipeline without breaking composability. The easiest way to do this would be as a last step via LINQ-to-Objects:
public IEnumerable<Product> GetProducts()
{
IQueryable<Product> result = from p in context.Products
where {Some Conditions 1}
select p;
return result.AsEnumerable().Select( x => {
x.SomeProp = context;
return x;
});
}
But note that this breaks composability - everything downstream is LINQ-to-Objects.
Since you have a common base-class / interface for your entities, this could alternatively be wrapped in an extension method for very similar behaviour (but better re-use):
return result.AssociateWith(context);
with something like:
public static IEnumerable<T> AssociateWith<T>(
this IEnumerable<T> source,
DataContext context)
where T : IEntityBase
{
foreach(T item in source)
{
item.DataContext = context;
yield return item;
}
}
这篇关于自定义的IQueryable&LT; T&GT;的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!