On Wed, 2009-03-25 at 21:13 +0100, Giacomo Tesio wrote:
> Thanks for the explain Jon!
>
> Ok... now I'm wondering about:
> - if the System.Data.Linq.DataContext would be such a base class, does
> we still need a Table<T> (when running in microsoft environment,
> obviously Mono ones still need it)?
So let's take a step back to further explain the problem, as Table<T> is
a really big problem.
Most people will be using generated code, as produced by sqlmetal or
DbMetal. This produces such wonderful code as:
class Northwind : DataContext {
public Table<Category> Categories { get { return
GetTable<Category>(); } }
// ...
}
So to do anything LINQ-ish, you need DataContext.GetTable<T>().
Next, what does Table<T> do? For DbLinq, it ties into QueryProvider<T>
(internal), and delegates many calls to (non-virtual) DataContext
members (e.g. Table<T>.Attach(T) calls DataContext.RegisterUpdate()).
So, for perfectly innocuous code like db.Categories.Attach(new Category
{...}), if we inherit from .NET's System.Data.Linq, we will get no
notification that the Attach() has occurred and that we need to add this
new value.
FAIL.
It gets worse. If we provide a shadowing DataContext.GetTable<T>()
method (as would be required, as GetTable<T>() isn't virtual), we could
return a new Table<T> instance. However, (1) Table<T> has an internal
constructor, (2) Table<T> is sealed, and (3) Table<T> has no virtual
methods. So even if we could create a new Table<T> instance (which we
can't), we couldn't customize the behavior at all.
Which is a long-winded way of saying that there's no way of
reusing .NET's Table<T> with our DataContext. The types are intimately
tied, and if we ever want to support entity tracking (which we do), we
need to provide our own Table<T> type.
For the same reasons, we also need to provide our own ChangeSet and
DataLoadOptions types..
Where we can reuse .NET types, we already do, e.g.
ChangeConflictCollection and IMultipleResults.
So when it comes down to it, we can't reuse most of .NET's DataContext
members, DataContext has no virtual members, and it can't be made to
work.
It's also a bad idea for OOP reasons, as DbLinq's DataContext IS NOT
A .NET DataContext (and can't be, due to the lack of virtuals).
> Actually I've to say that what I'd like to depend on microsoft code to
> allow code depending on the System.Data.Linq.DataContext to be
> injected with DbLinq code without too much refactoring.
I don't understand what you mean by this. How would we "inject" DbLinq
code?
> What I'm saing is to remove some code from the
> DbLinq.Data.Linq.DataContext by deriving it from the
> System.Data.Linq.DataContext: both the DbLinq version of the
> System.Data.Linq.DataContext and the linq to sql one should have the
> same signature (and the same satellite classes).
> So by changing the reference of a project we would get the microsoft
> one or the our.
That's already possible now: change the assembly reference from
System.Data.Linq.dll to DbLinq.dll, and change 'using System.Data.Linq;'
to 'using DbLinq.Data.Linq', and that should work.
Even with your approach, this would be required (unless you wanted
DbLinq's DataContext to also be in the System.Data.Linq namespace, which
is just begging for trouble).
So from what I can see, your proposal would offer no benefits over what
is currently being done.
> We would really need to override such non virtual methods? Or we need
> to have such methods on our datacontext becouse it doesn't extend the
> System.Data.Linq.DataContext?
What you're missing is the overall picture of what Linq-to-SQL does.
In a nutshell, Linq-to-SQL takes a linq expression
var pens = from p in db.Products
where p.ProductName == "Pen"
select p;
And translates this at runtime into the equivalent of the SQL 'select *
from Products where ProductName = "Pen"'.
This sounds simple, but it isn't, as each vendor has a slightly
different dialect of SQL. Consequently, DbLinq needs to be intimately
involved in the SQL generation process, with access to all expression
trees, etc., in order to generate the resulting SQL.
Why the intimacy? Through normal LINQ, the above code is translated
into:
db.Products.Where(p => p.ProductName == "Pen").Select(p => p);
Since we're dealing with IQueryable<T> and not IEnumerable<T>, this
actually calls e.g. System.Linq.Queryable.Select(), which for Mono is:
public static IQueryable<TResult> Select<TSource, TResult> (this
IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector)
{
Check.SourceAndSelector (source, selector);
return source.Provider.CreateQuery<TResult> (
StaticCall (
MakeGeneric (MethodBase.GetCurrentMethod (),
typeof (TSource), typeof (TResult)),
source.Expression,
Expression.Quote (selector)));
}
Note, specifically, the source.Provider reference. For the above,
source would be db.Products, which is Table<Product>, thus
source.Provider would be Table<T>.Provider, which is explicitly
implemented, and will refer to a type internal to System.Data.Linq.
Thus, the linchpin of the whole process, the IQueryable interface, is
buried deep within Linq to SQL with no way to intervene and customize
its behavior.
This is why the lack of virtual members in DataContext (and everything
else) is a killer: without any virtual members, without any way to be
involved in the IQueryable<T> to SQL conversion process, there is no way
to generate custom SQL for specific databases. And without custom SQL,
you don't have support for databases other than SQL Server. And without
such support...what's the point?
- Jon
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"DbLinq" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/dblinq?hl=en
-~----------~----~----~----~------~----~------~--~---