Depuis déjà deux ans, nous utilisons Linq-To-Sql dans la grande majorité de nos développements basés sur Sql Server 2005 / 2008. Dans une première phase, nous avions refactorisé la majorité de notre code pour passer de DataSets + Store procédure vers Linq-To-Sql. Le gain en maintenance et en simplicité en valait tout simplement l’effort.Nous n’avons rien regretté depuis.
Suite à cette phase de refactorisation, nous avons regardé pour améliorer notre approche de développement en utilisant les extensions de méthodes jumelé à la réécriture d’expressions. Et une fois de plus, nous venons de réaliser quelque chose de phénoménale par cette approche.
Il faut d’abord comprendre que lorsque l’on écrit une requête Linq-To-Sql c’est en fait une arborescence d’expression qui est créé. Cette arborescence décrit en termes d’objet comment on désire effectuer la requête. Cette arborescence est ensuite envoyé à l’optimisateur Linq qui va la transformer en requête Sql.
En résumé, nous avons donc ces étapes:
Regardons un exemple… lorsque l’on écrit cette requête Linq :
var x = from p in db.Persons
where p.FirstName.StartsWith(« C »)
select p;
On obtient cette arborescence d’expression :
___ex0 > MethodCallExpression
Method : MethodInfo : « Where<Person> »
Object : Expression : null
Arguments : ReadOnlyCollection<Expression>
___ex1 > ConstantExpression
Value : Object : « Table(Person) »
NodeType : ExpressionType : « Constant »
Type : Type : « Table<Person> »
___ex2 > UnaryExpression
Operand : ExpressionLambda
___ex3 > Expression<Func<Person, Boolean>>
Body : ExpressionCall
___ex4 > MethodCallExpression
Method : MethodInfo : « StartsWith »
Object : ExpressionMemberAccess
___ex5 > MemberExpression
Expression : ExpressionParameter
___ex6 > ParameterExpression
Name : String : « p »
NodeType : ExpressionType : « Parameter »
Type : Type : « Person »
Member : MemberInfo : « System.String FirstName »
NodeType : ExpressionType : « MemberAccess »
Type : Type : « String »
Arguments : ReadOnlyCollection<Expression>
___ex7 > ConstantExpression
Value : Object : « C »
NodeType : ExpressionType : « Constant »
Type : Type : « String »
NodeType : ExpressionType : « Call »
Type : Type : « Boolean »
Parameters : ReadOnlyCollection<ParameterExpression>
___ex6 > ParameterExpression
Name : String : « p »
NodeType : ExpressionType : « Parameter »
Type : Type : « Person »
NodeType : ExpressionType : « Lambda »
Type : Type : « Func<Person, Boolean> »
Method : MethodInfo : null
IsLifted : Boolean : « False »
IsLiftedToNull : Boolean : « False »
NodeType : ExpressionType : « Quote »
Type : Type : « Expression<Func<Person, Boolean>> »
NodeType : ExpressionType : « Call »
Type : Type : « IQueryable<Person> »
Ensuite, l’optimisateur roule sur l’arborescence une fois que l’on demande à aller chercher l’info de la base de données, et transforme cette grosse arborescence en cette petite requête :
SELECT [t0].[PersonID], [t0].[FirstName], [t0].[Initials], [t0].[LastName], [t0].[IMAddress], [t0].[Birthdate], [t0].[Timestamp], [t0].[ContactID], [t0].[PersonTitleID], [t0].[Sex], [t0].[ProfessionalTitle], [t0].[Notes]
FROM [dbo].[Person] AS [t0]
WHERE [t0].[FirstName] LIKE ‘C%’
Où nous pouvons gagner des points très intéressants, c’est en comprenant bien la nature de l’arborescence… Car une fois que l’on comprend comment elle représente la requête Linq, on peut essayer de la modifier !En poursuivant avec le même exemple, imaginons que l’on désire filtrer la table sans utiliser le where…
On pourrait par exemple écrire cette requête Linq :
var x = from p in db.Persons.FilterByFirstName(« C »)
select p;
Qui sort les « p » non plus de db.Persons, mais de db.Persons.FilterByFirstName() !
Comment définir cette nouvelle méthode ? Via les extensions de méthodes ! Une classe statique qui contient les extensions. Chaque extensions de méthode a son premier paramètre préfixé du mot réservé « this ».
Voici le code pour cet exemple :
public static class LinqExtenders
{
public static IQueryable<Person> FilterByFirstName(this IQueryable<Person> persons, string s)
{
return from p in persons
where p.FirstName.StartsWith(s)
select p;
}
}
Simple n’est ce pas ? Cette extension de méthode permet de modifier l’arborescence d’expression de manière très simple. Nous avons donc une arborescence différente de la précédente qui contenait 6 expression, alors que cette nouvelle en contient le double :
___ex0 > MethodCallExpression
Method : MethodInfo : « Select<Person, Person> »
Object : Expression : null
Arguments : ReadOnlyCollection<Expression>
___ex1 > MethodCallExpression
Method : MethodInfo : « Where<Person> »
Object : Expression : null
Arguments : ReadOnlyCollection<Expression>
___ex2 > ConstantExpression
Value : Object : « Table(Person) »
NodeType : ExpressionType : « Constant »
Type : Type : « Table<Person> »
___ex3 > UnaryExpression
Operand : ExpressionLambda
___ex4 > Expression<Func<Person, Boolean>>
Body : ExpressionCall
___ex5 > MethodCallExpression
Method : MethodInfo : « StartsWith »
Object : ExpressionMemberAccess
___ex6 > MemberExpression
Expression : ExpressionParameter
___ex7 > ParameterExpression
Name : String : « p »
NodeType : ExpressionType : « Parameter »
Type : Type : « Person »
Member : MemberInfo : « System.String FirstName »
NodeType : ExpressionType : « MemberAccess »
Type : Type : « String »
Arguments : ReadOnlyCollection<Expression>
___ex8 > MemberExpression
Expression : ExpressionConstant
___ex9 > ConstantExpression
Value : Object : « WpfApplication1.LinqExtenders+<>c__DisplayClass0″
NodeType : ExpressionType : « Constant »
Type : Type : « <>c__DisplayClass0″
Member : MemberInfo : « System.String s »
NodeType : ExpressionType : « MemberAccess »
Type : Type : « String »
NodeType : ExpressionType : « Call »
Type : Type : « Boolean »
Parameters : ReadOnlyCollection<ParameterExpression>
___ex7 > ParameterExpression
Name : String : « p »
NodeType : ExpressionType : « Parameter »
Type : Type : « Person »
NodeType : ExpressionType : « Lambda »
Type : Type : « Func<Person, Boolean> »
Method : MethodInfo : null
IsLifted : Boolean : « False »
IsLiftedToNull : Boolean : « False »
NodeType : ExpressionType : « Quote »
Type : Type : « Expression<Func<Person, Boolean>> »
NodeType : ExpressionType : « Call »
Type : Type : « IQueryable<Person> »
___ex10 > UnaryExpression
Operand : ExpressionLambda
___ex11 > Expression<Func<Person, Person>>
Body : ExpressionParameter
___ex12 > ParameterExpression
Name : String : « p »
NodeType : ExpressionType : « Parameter »
Type : Type : « Person »
Parameters : ReadOnlyCollection<ParameterExpression>
___ex12 > ParameterExpression
Name : String : « p »
NodeType : ExpressionType : « Parameter »
Type : Type : « Person »
NodeType : ExpressionType : « Lambda »
Type : Type : « Func<Person, Person> »
Method : MethodInfo : null
IsLifted : Boolean : « False »
IsLiftedToNull : Boolean : « False »
NodeType : ExpressionType : « Quote »
Type : Type : « Expression<Func<Person, Person>> »
NodeType : ExpressionType : « Call »
Type : Type : « IQueryable<Person> »
Mais qui génère toujours le même Sql en bout de ligne, comme quoi l’optimisateur fait bien son travail !
SELECT [t0].[PersonID], [t0].[FirstName], [t0].[Initials], [t0].[LastName], [t0].[IMAddress], [t0].[Birthdate], [t0].[Timestamp], [t0].[ContactID], [t0].[PersonTitleID], [t0].[Sex], [t0].[ProfessionalTitle], [t0].[Notes]
FROM [dbo].[Person] AS [t0]
WHERE [t0].[FirstName] LIKE ‘C%’
Et maintenant, imaginez ce que l’on peut accomplir en remplaçant le filtre simple que nous avions par un filtre plus évolué, qui construit dynamiquement des relations avec plusieurs familles de joins, ajoute dynamiquement de nouveaux prédicats à la requête, change les sources des tables en fonction de paramètre…
C’est ce que nous avons accomplis pour une nouvelle réussite aujourd’hui !
Voici un autre bel exemple de la puissance des requêtes Linq. On avait déjà plusieurs centaines de requêtes Linq comme celle-ci dans le code :
from zh in dbContext.ZoneHierarchies.Filter(args)
join z in dbContext.Zones on zh.ZoneID equals z.ZoneID
join zl in dbContext.ZoneLocales.FilterCulture(args) on z.ZoneID equals zl.ZoneID
join zp in dbContext.ZonePages.FilterCulture(args, « ZoneID », new string[] { }) on z.ZoneID equals zp.ZoneID
where (((args.ParentZoneHierarchyID == null) && (!zh.ParentZoneHierarchyID.HasValue)) ||
(args.ParentZoneHierarchyID.GetValueOrDefault(-1) == zh.ParentZoneHierarchyID))
&& ((args.ZoneRequestType == ZoneRequestResultType.All)
|| (((args.ZoneRequestType == ZoneRequestResultType.OnlyPages) && (z.ZoneType == (int)ZoneType.Page))
|| ((args.ZoneRequestType == ZoneRequestResultType.OnlyDocuments) && (z.ZoneType == (int)ZoneType.Document))))
orderby zp.Order, zp.Title
select EntityFactory.CreateZoneFileEntityFromDatabase<ZoneFileEntity>(zh, zp, zl, (ZoneType)z.ZoneType, args, dbContext);
Pas trop compliqué, elle se lit quand même bien et indique bien son intention. Et sur ces requêtes, on avait déjà des méthodes extensions pour filtrer en fonction de statuts, date d’activations, cultures, …
Et là, on a implémenté dans le code des filtres existants, des filtres supplémentaires pour gérer notre nouvelle structure avec version de contenues (pouvoir supporter de retourner en arrière dans des enregistrements de tables).
Et voilà, toutes les requêtes écrites avec ces petits bouts de Linq supportent désormais le versionnage des tables ! Le petit peu de linq qui est là et se lit très bien génère la grosse requête SQL du bas, qui est assez incompréhensible à première vues.
SELECT [t0].[ZoneHierarchyID], [t0].[ZoneID], [t0].[ParentZoneHierarchyID], [t0].[ActiveFrom], [t0].[ActiveTo], [t0].[Status], [t9].[ZoneID] AS [ZoneID2], [t9].[Title], [t9].[FileName], [t9].[CultureID], [t9].[ActiveFrom] AS [ActiveFrom2], [t9].[ActiveTo] AS [ActiveTo2], [t9].[Status] AS [Status2], [t9].[ZonePageID], [t9].[Order] AS [Order], [t9].[ModificationDate], [t9].[ModifiedByName], [t9].[ModifiedByContactGuid], [t9].[PublicationVersion], [t5].[ZoneID] AS [ZoneID3], [t5].[CultureID] AS [CultureID2], [t5].[ZoneLocaleName], [t5].[ChildrenDirectoryName], [t5].[ActiveFrom] AS [ActiveFrom3], [t5].[ActiveTo] AS [ActiveTo3], [t5].[Description], [t5].[Status] AS [Status3], [t5].[ZoneLocaleID], [t5].[ModificationDate] AS [ModificationDate2], [t5].[ModifiedByName] AS [ModifiedByName2], [t5].[ModifiedByContactGuid] AS [ModifiedByContactGuid2], [t5].[PublicationVersion] AS [PublicationVersion2], [t1].[ZoneType] AS [zt]
FROM [Content].[ZoneHierarchy] AS [t0]
INNER JOIN [Content].[Zone] AS [t1] ON [t0].[ZoneID] = [t1].[ZoneID]
INNER JOIN (
SELECT DISTINCT [t3].[ZoneID], [t3].[CultureID], [t3].[ZoneLocaleName], [t3].[ChildrenDirectoryName], [t3].[ActiveFrom], [t3].[ActiveTo], [t3].[Description], [t3].[Status], [t3].[ZoneLocaleID], [t3].[ModificationDate], [t3].[ModifiedByName], [t3].[ModifiedByContactGuid], [t3].[PublicationVersion]
FROM [Content].[ZoneLocale] AS [t2]
CROSS JOIN ([Content].[ZoneLocale] AS [t3]
INNER JOIN [Version].[ZoneLocaleVersionIndex] AS [t4] ON [t3].[ZoneLocaleID] = [t4].[ZoneLocaleID])
WHERE [t4].[IndexType] = 1
) AS [t5] ON [t1].[ZoneID] = [t5].[ZoneID]
INNER JOIN (
SELECT DISTINCT [t7].[ZoneID], [t7].[Title], [t7].[FileName], [t7].[CultureID], [t7].[ActiveFrom], [t7].[ActiveTo], [t7].[Status], [t7].[ZonePageID], [t7].[Order], [t7].[ModificationDate], [t7].[ModifiedByName], [t7].[ModifiedByContactGuid], [t7].[PublicationVersion]
FROM [Content].[ZonePage] AS [t6]
CROSS JOIN ([Content].[ZonePage] AS [t7]
INNER JOIN [Version].[ZonePageVersionIndex] AS [t8] ON [t7].[ZonePageID] = [t8].[ZonePageID])
WHERE [t8].[IndexType] = 1
) AS [t9] ON [t1].[ZoneID] = [t9].[ZoneID]
WHERE ((NOT ([t0].[ParentZoneHierarchyID] IS NOT NULL)) OR (-1 = [t0].[ParentZoneHierarchyID])) AND ([t0].[ActiveFrom] <= ’12/09/2009 15:07:24′) AND (’12/09/2009 15:07:24′ <= [t0].[ActiveTo]) AND ([t5].[ActiveFrom] <= ’12/09/2009 15:07:24′) AND (’12/09/2009 15:07:24′ <= [t5].[ActiveTo]) AND ([t5].[CultureID] = REPLACE((
SELECT [t12].[value]
FROM (
SELECT TOP (1) [t11].[value]
FROM (
SELECT
(CASE
WHEN 0 = 1 THEN CONVERT(NVarChar(10),’1′)
WHEN (([t10].[Status] & 4) = 4) AND ([t10].[CultureID] = ‘ ‘) THEN CONVERT(NVarChar(10),’ZZZInv’)
WHEN ([t10].[CultureID] = ‘ ‘) OR ([t10].[CultureID] = ‘ ‘) THEN CONVERT(NVarChar(10),REPLACE([t10].[CultureID], ‘ ‘, ’1′))
WHEN [t10].[CultureID] <> ‘ ‘ THEN CONVERT(NVarChar(10),’0′)
ELSE CONVERT(NVarChar(10),’1′)
END) AS [value], [t10].[ZoneLocaleID]
FROM [Content].[ZoneLocale] AS [t10]
) AS [t11]
WHERE [t11].[ZoneLocaleID] = [t5].[ZoneLocaleID]
ORDER BY [t11].[value] DESC
) AS [t12]
), ’1′, ‘ ‘)) AND ([t9].[ActiveFrom] <= ’12/09/2009 15:07:24′) AND (’12/09/2009 15:07:24′ <= [t9].[ActiveTo]) AND ([t9].[CultureID] = REPLACE((
SELECT [t15].[value]
FROM (
SELECT TOP (1) [t14].[value]
FROM (
SELECT
(CASE
WHEN 0 = 1 THEN CONVERT(NVarChar(10),’1′)
WHEN (([t13].[Status] & 4) = 4) AND ([t13].[CultureID] = ‘ ‘) THEN CONVERT(NVarChar(10),’ZZZInv’)
WHEN ([t13].[CultureID] = ‘ ‘) OR ([t13].[CultureID] = ‘ ‘) THEN CONVERT(NVarChar(10),REPLACE([t13].[CultureID], ‘ ‘, ’1′))
WHEN [t13].[CultureID] <> ‘ ‘ THEN CONVERT(NVarChar(10),’0′)
ELSE CONVERT(NVarChar(10),’1′)
END) AS [value], [t13].[ZoneID]
FROM [Content].[ZonePage] AS [t13]
) AS [t14]
WHERE [t14].[ZoneID] = [t9].[ZoneID]
ORDER BY [t14].[value] DESC
) AS [t15]
), ’1′, ‘ ‘))
ORDER BY [t9].[Order], [t9].[Title]
Observation ? En modifiant à un seul endroit comment on appliqué les filtres sur les tables, on a modifié dynamiquement des centaines de requêtes, et un seul endroit pour en effectuer la maintenance. Quel économie d’échelle !
Mais en toute honnêteté, c’est quand même beaucoup de travail pour aller altérer la requête. Voici un exemple du travail qui a été accompli pour supporter les nouvelles requêtes de versionages aux bases de données.
public static IQueryable<T> FilterVersion<T>(this IQueryable<T> t, IVersionIndexFilter version)
where T : class
{
try
{
// Défénie le type de la table VersionIndex
Type versionTableType = Type.GetType(typeof(T).AssemblyQualifiedName.Replace(typeof(T).FullName, string.Concat(typeof(T).FullName, VersionIndexTableExtensionName)));
// Obtient la méthod GetTable typer avec le type de la table versionIndex donc GetTable<versionTableType>
var getTableType = MethodInfoHelper.GetGenericMethod(typeof(DataContext), « GetTable », new Type[] { versionTableType }, new Type[] { }, typeof(Table<>).MakeGenericType(typeof(Table<>).GetGenericArguments()[0]), BindingFlags.Public | BindingFlags.Instance);
// Créer un instance de la table de jointure
var versionIndexTable = getTableType.Invoke(((Table<T>)t).Context, null);
// NOTE(cboivin): Valide que cette table n’est pas null
if (versionIndexTable != null)
{
// NOTE(cboivin): Récupère le champs de relations entre la table index et la vrai table
string relationKey = GetTableFilterVersionRelationKey(t as Table<T>);
// NOTE(cboivin): Défénie le type générique de la table VersionIndex
var innerTableType = typeof(Table<>).MakeGenericType(versionTableType);
// Défénie une class de conversion
var genericClassConverter = typeof(ConvertTable<,>).MakeGenericType(typeof(T), versionTableType);
// NOTE(cboivin): Défénie un paramètre pour accèder à la table primaire
ParameterExpression inputTableParameter = (ParameterExpression)ParameterExpression.Parameter(typeof(T), « leftTable »);
// NOTE(cboivin): Défénie un paramètre pour accèder à la table de jointure
ParameterExpression versionIndexTableParameter = (ParameterExpression)ParameterExpression.Parameter(versionTableType, « rigthTable »);
// NOTE(cboivin): Défénie une nouvelle expression qui construit notre class générique typer
NewExpression genericClassExpression = Expression.New(genericClassConverter.GetConstructor(new Type[] { typeof(T), versionTableType }), new List<Expression>() { inputTableParameter, versionIndexTableParameter }, genericClassConverter.GetProperty(« FromT »), genericClassConverter.GetProperty(« InnerT »));
// NOTE(cboivin): Créer un paramètre sur notre class générique
ParameterExpression genericClassParameter = Expression.Parameter(genericClassExpression.Type, « item »);
// NOTE(cboivin): Défénie un accès sur la propriété FromT de notre class générique
Expression inputTableMemberAccess = Expression.MakeMemberAccess(genericClassParameter, genericClassConverter.GetProperty(« FromT »));
// NOTE(cboivin): Défénie un accès sur la propriété InnerT de notre class générique
Expression versionIndexTableMemberAccess = Expression.MakeMemberAccess(genericClassParameter, genericClassConverter.GetProperty(« InnerT »));
// NOTE(cboivin): Défénie la lambda qui donne access au fromT vers la class générique
Expression fromTOfGenericClassLambda = Expression.Lambda(inputTableMemberAccess, genericClassParameter);
// NOTE(cboivin): Défénie la propriété GenericFromTAccess
Expression fromTOfGenericClassEvaluated = Expression.Quote(fromTOfGenericClassLambda);
// NOTE(cboivin): Défénie la lambda qui contiendra nos deux propritété
Expression genericClassWithProperty = Expression.Lambda(genericClassExpression, inputTableParameter, versionIndexTableParameter);
// NOTE(cboivin): Défénie la class généric evaluer en expression
Expression genericClassWithPropertyEvaluated = Expression.Quote(genericClassWithProperty);
// NOTE(cboivin): Défénie la propriété sur la table Versionner qui servira à faire le inner join
MemberExpression versionIndexTableKeyField = Expression.PropertyOrField(versionIndexTableParameter, relationKey);
// NOTE(cboivin): Défénie la lambda qui relie la table à sa propriété
Expression versionIndexTableWithKeyPropertyLambda = Expression.Lambda(versionIndexTableKeyField, versionIndexTableParameter);
// NOTE(cboivin): Evalue l’expression qui contient la table versionner et sa propriété
UnaryExpression versionIndexTableWithKeyEvaluated = Expression.Quote(versionIndexTableWithKeyPropertyLambda);
// NOTE(cboivin): Défénie le champs qui servira à faire la relations entre les deux table pour la table d’entrée
MemberExpression inputTableKeyField = Expression.PropertyOrField(inputTableParameter, relationKey);
// NOTE(cboivin): Défénie la lambda qui contiendra la table d’entré et le champs de relations
Expression inputTableWithKeyPropertyLambda = Expression.Lambda(inputTableKeyField, inputTableParameter);
// NOTE(cboivin): Défénie la table et son champs de propriété qui servira à faire la relations
UnaryExpression inputTableWithKeyEvaluated = Expression.Quote(inputTableWithKeyPropertyLambda);
// NOTE(cboivin): Défénie la constante de table d’entrée
ConstantExpression inputTableIQueryable = (ConstantExpression)Expression.Constant(t);
// NOTE(cboivin): Défénie la constante de table de version
ConstantExpression versionIndexTableIQueryable = (ConstantExpression)Expression.Constant(versionIndexTable, innerTableType);
// NOTE(cboivin): Série d’expression générique pour retourver le method info Join
var typeEnumerable = typeof(IEnumerable<>);
var typeExpOuterFunc = typeof(Func<,>);
var typeExpOuterKey = typeof(Expression<>).MakeGenericType(typeExpOuterFunc).GetGenericTypeDefinition();
var typeInnerFunc = typeof(Func<,>);
var typeExpInnerKey = typeof(Expression<>).MakeGenericType(typeInnerFunc).GetGenericTypeDefinition();
var typeExpResult = typeof(Func<,,>);
var typeExpResulSelector = typeof(Expression<>).MakeGenericType(typeExpResult).GetGenericTypeDefinition();
// NOTE(cboivin): Récupère le method info join, et envoie les bon paramètre pour qu’il revienne typer
var joinMethodInfo = MethodInfoHelper.GetGenericMethod(typeof(Queryable), « Join »,
new Type[] { typeof(T), versionTableType, typeof(int), genericClassConverter },
new Type[] { typeof(IQueryable<>), typeEnumerable, typeExpInnerKey, typeExpOuterKey, typeExpResulSelector },
typeof(IQueryable<>).GetGenericTypeDefinition());
// NOTE(cboivin): Défénie le call expression pour le join
var joinCallMethodInfo =
Expression.Call(
(Expression)null,
joinMethodInfo,
inputTableIQueryable,
versionIndexTableIQueryable,
inputTableWithKeyEvaluated,
versionIndexTableWithKeyEvaluated,
genericClassWithPropertyEvaluated);
// NOTE(cboivin): Récupère le method info pour le where
var whereMethodInfo = MethodInfoHelper.GetGenericMethod(typeof(Queryable), « Where », new Type[] { genericClassConverter }, new Type[] { typeof(IQueryable<>), typeof(Expression<>).MakeGenericType(typeof(Func<,>)).GetGenericTypeDefinition() }, typeof(IQueryable<>));
// NOTE(cboivin): Défénie la valeurs du index type du filter
Expression enumValue = Expression.Constant(((int)version.IndexType));
// NOTE(cboivin): Créer un member access sur l’objet InnerT qui est la table versionIndex
Expression indexTypeMemberAccess = Expression.MakeMemberAccess(versionIndexTableMemberAccess, versionTableType.GetProperty(« IndexType »));
// NOTE(cboivin): Défénie l’expression de comparaison binaire entre le member access et ca valeurs
Expression indexTypeEqualExpression = BinaryExpression.Equal(indexTypeMemberAccess, enumValue);
// NOTE(cboivin): Défénie la lambda where == sur la class généric pour l’index type
Expression whereVersion = Expression.Lambda(indexTypeEqualExpression, genericClassParameter);
// NOTE(cboivin): Évalue le where
Expression whereVersionQuote = Expression.Quote(whereVersion);
// NOTE(cboivin): Expression Call du where
Expression whereCall = Expression.Call((Expression)null, whereMethodInfo, joinCallMethodInfo, whereVersionQuote);
// NOTE(cboivin): Récupère le method info select
var selectMethodInfo = MethodInfoHelper.GetGenericMethod(typeof(Queryable), « Select », new Type[] { typeof(ConvertTable<,>).MakeGenericType(t.GetType().GetGenericArguments()[0], versionTableType), typeof(T) }, new Type[] { typeof(IQueryable<>), typeof(Expression<>).MakeGenericType(typeof(Func<,>)).GetGenericTypeDefinition() }, typeof(IQueryable<>).GetGenericTypeDefinition());
// NOTE(cboivin): Créer le select expression call
Expression selectExpressionCall = Expression.Call((Expression)null, selectMethodInfo, whereCall, fromTOfGenericClassEvaluated);
// NOTE(cboivin): Créer la lambda du select
Expression<Func<T,IQueryable<T>>> selectLambda = (Expression<Func<T, IQueryable<T>>>)Expression.Lambda(selectExpressionCall, inputTableParameter);
// NOTE(cboivin): S’assure de sortir les résultat distinctement, en IQueryable<T>
return (from a in t.Select(selectLambda) select a).SelectMany(i=> i).Distinct();
}
}
catch (Exception ex)
{
Console.Write(ex.Message);
}
return t;
}
Alors si vous désirez suivre ce chemin, il y a un bon retour sur l’investissement, mais c’est complexe. Il faut d’abord maîtriser la réflexivité, les génériques, les delegates lambdas, les méthodes extensions, Linq et les expressions…
Alors attelez-vous, il n’y a pas beaucoup de documentations, et c’est encore très nouveau comme approche. Mais quel puissance !
Wow !! Quel puissance derrière ce code
!!
Bien le bonjour!
Avez-vous pensé utiliser ADO.NET Entity Framework à la place?
J’ai trouvé qu’il y avait des limitations avec LINQ-To-SQL (entre-autres avec les relations Many-to-Many et les mise-à-jour de la BD). De plus, j’ai lu que LINQ-To-SQL sera discontinué.
Enfin, un petit pointeur pour vous avant qu’il ne soit trop tard!
À bientôt,
Yannick
@Yannick
Bonjour mister !
Oui nous avons regardé avec Entity Framework un peu. Et notre conclusion était d’attendre la seconde mouture, disponible avec VS 2010.
http://efvote.wufoo.com/forms/ado-net-entity-framework-vote-of-no-confidence/
Pour l’heure, Linq-to-Sql c’est plaisant dans le sens que les performances y sont. Les benchmark sont agréables aussi en comparaison à Linq-to-Entity. C’est pas tellement un ORM, comme une abstraction directe de la BD…
En tout cas, on a beaucoup d’espoir pour .Net 4 !