问题
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);
}
}
回答1:
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;
}
}
回答2:
The Wayward Weblog has a great tutorial for working with IQueryable and the a related toolkit. Looks like you found a solution that works for you.
I'm not sure why you would want to do this unless you are trying to make LINQ to SQL entities follow the Active Record pattern. If that is your aim, I would recommend adding static GetById, Query, Insert, Update, and Delete methods on your base and use extension methods to add the methods to the entities. Inside each, you could create a new data context and attach the entity to that context when you are ready to perform the action.
The DataContext follows the unit of work pattern, and as such, should be kept alive only a short while as you complete the action you perform.
来源:https://stackoverflow.com/questions/486947/customizing-iqueryablet