Table<TEntity> constraint
In Beta2, TEntity is constrained to be a class; there was no such constraint in Beta 1. Unfortunately, constraining TEntity to be a class eliminates a very useful pattern. Suppose that I have a collection of tables T1, T2, ..., TN that share a common structure. I could define an interface that defines this structure and declare that the corresponding table classes implement an interface I that defines the common features, e.g.,
Code Snippet
partial class T1 : I
{
}
partial class T2 : I
{
}
//..etc
I am then free to treat these tables in a generic way and query Table<I> instances. This is no longer possible.
Yes, I know there are work-arounds and I have worked around but I don't understand why this should be necessary. It was very convenient to just define common features in an interface and now I can't do that. Can anyone explain this?
Thanks.
You could not have done that anyway. Interfaces are not mappable since there is no obvious means of constructing one.
I think you missed my point; I'm not suggesing "mapping" the interfaces, per se, but allowing the entity types to implement the interface and then programatically manipulate Table instances where I is some interface. This methodology worked just fine during Beta 1 but was broken in Beta 2 due to the class constraint I mentioned. Again, by way of example, suppose that I have a certain category of tables, say "name tables" where a convention is agreed upon that all tables in that category will defined a column called "Name" of type varchar(64). Suppose we have three such name tables in the database: NameTableA, NameTableB and NameTableC that define the required column. Now, I can define an interface, call it INamedTable thusly:
Code Snippet
interface INamedTable
{
string Name {get; set;
}
Now, if I run SQL Metal over my DB, it generates the partial classes NamedTableA, NamedTableB and NamedTableC. In a separate file, I could write
Code Snippet
partial class NamedTableA : INamedTable
{}
partial class NamedTableB : INamedTable
{}
partial class NamedTableC : INamedTable
{}
So, what does this buy me? It buys me the ability to manipulate Table instances generically. So, for example, I could write a method such as
Code Snippet
IEnumerable<string> GetNames(Table<INamedTable> table)
{
return( from row in table select row.Name);
}
This is no longer possible and I see no good reason why.
You may have been able to declare the signature of Table<SomeInterface> but you would not have been able to create and use one. Table<T>'s are created by the DataContext, where the T itself determines the mapping. Interfaces are not mapped and so would produce an exception when trying to create the Table instance.
I'm not sure what I can say to make my point clearer; If I have tables that share a common structure I need to be able to treat them in a common way. Declaring that they implement a given interface allows this. No, the interface itself is not mapped but it allows access to members that *are* mapped without any extra effort. This works because the property declarations in the interface match the Table<> property declarations. If I have a Table<I> instance where I is some known interface, I can manipulate Table<I> without knowing the concrete type that actually implments I. In Beta 1, for example, I had several query algorithms that worked on this principle that all broke when I moved to Beta 2.
Does anyone else here see my point or am I whistling in the wind?
I see your point, and I'm not debating the merit of doing what you say. I'm just telling you that LINQ to SQL was not designed to work with interfaces as entity types, so it is unclear to me how you could have gotten this to work before Beta 2. I realize you intend to have actual classes for the entities and just want to by polymorphic over them by using a common interface, but the only way that LINQ to SQL works is for you to declare Table<T> properties on the DataContext where the T represents a class that has mapping information, and these T's can only be materialized if the underlying technology can instantiate one via a default constructor with or without the generic constraint on Table<T>'s T. Table<T>'s are not meant to be used for any other purpose, so its unclear to me not only how you got them to be instantiated with interfaces but how they actually worked that way at all.
Likewise, if your only intent was to be able to be somewhat polymorphic over a set of tables of some base interface type in the signature of a method, I'm not seeing how that was possible either. CLR generics are not covariant, so you can't start with a Table<SomeClass> and cast it to Table<SomeInterface>
Ok, here is a simple example of how this worked in Beta 1:
Suppose there are tables called Country and State that both have columns named Id and Name; let's call these enumeration tables.
Running SQL Metal on the DB produces classes Country and State that have correspondingly named properties.
I define an interface called IEnumerationTable that declared two properties, Id and Name. In a separate file, I declare:
Code Snippet
partial class State : IEnumerationTable {}
partial class Country : IEnumerationTable {}
Now, define a "transporter" class that is responsible for querying enumeration tables; for the sake of brevity, I will leave out a considerable amount of detail. Anyway, this is the gist:
Code Snippet
class EnumTransporter<T> where T : IEnumerationTable
{
public T SelectEnum(int id)
{
Table<T> table = DataContext.GetTable<T>();
IQueryable<T> results = from row in table where row.Id == id select row
return results.FirstOrDefault();
}
}
So, as you can see, the transporter itself is instantiated with a concrete type whose properties ARE mapped and then the properties themselves are accessible via the interface. I can assure you that this approach worked beautifully...and became totally busted when the class contraint was added. The workaround is not nearly as elegant and is considerably more work.