自定义IQueryable <t> </t>

时间:2009-01-28 08:58:32

标签: .net linq-to-sql iqueryable

我正在尝试自定义应用程序的实体,以使它们具有引用加载它们的DataContext的属性。

我认为最好的方法是以某种方式创建一个实现IQueryable的类,并在其GetEnumerator方法中设置实体datacontext属性。

我的问题是,如何在我的IQueryable实现中使用Linq to SQL使用的Provider和Expression,以便我自己不必实现它们?

BTW:对于我的情况,还有另一种方式吗?

看看以下代码:

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; }; 
} 

更新

我自己找到了答案。这是示例代码,用于说明我是如何做到的。

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);
    }         
}

2 个答案:

答案 0 :(得分:1)

我想不出一个简单的方法。您不能在不破坏可组合性的情况下注入LINQ-to-SQL管道的中间位置。最简单的方法是通过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;
    });
}

但请注意,这会破坏可组合性 - 下游的所有内容都是LINQ-to-Objects。

由于您的实体具有公共基类/接口,因此可以将其包装在扩展方法中,以实现非常相似的行为(但更好地重复使用):

   return result.AssociateWith(context);

有类似的东西:

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;
    }
}

答案 1 :(得分:0)

Wayward Weblog有一个很棒的tutorial for working with IQueryable和一个相关的toolkit。看起来您找到了适合您的解决方案。

除非您尝试使LINQ to SQL实体遵循Active Record模式,否则我不确定您为什么要这样做。如果这是你的目标,我建议你在你的基础上添加静态GetById,查询,插入,更新和删除方法,并使用扩展方法将方法添加到实体。在每个内部,您可以创建新的数据上下文,并在准备好执行操作时将实体附加到该上下文。

DataContext遵循unit of work pattern,因此,当您完成所执行的操作时,应该是kept alive only a short while