1

les gens! Supposons que nous ayons un ensemble d'interfaces qui présente le problème 'domain: IUser, IAddressBook, IComment et ainsi de suite. Le supposer que IUser est défini comme suit:Comment faire en sorte que Linq2Sql comprenne les types personnalisés?

public interface IUser : IAmIdentifiedEntity<int>, IHaveName 
{ 
    string FullName { get; set; } 
    string Email { get; set; } 
    bool ReceiveNotification { get; set; } 
} 

public interface IHaveName 
{ 
    string Name { get; set; } 
} 

Dans ma demande, j'utilise des contrats seulement mentionnés, par exemple:

public IUser GetUser(string userName) 
{ 
    return Warehouse.GetRepository<IUser>().GetAll() 
     .First(u => u.Name == userName); 
} 

Comme vous pouvez le voir, je suis en utilisant une passerelle pour obtenir des données. La méthode GetAll() du référentiel renvoie IQueryable<TEntity>, il est donc possible de générer une requête complexe et d'utiliser tous les avantages du chargement paresseux. Quand je l'ai présenté, j'ai pensé à l'appareil de Linq2Sql à l'avenir. Pendant un certain temps, lors du développement du code client, nous utilisions l'implémentation «en mémoire» du stockage de données. Donc tout fonctionne bien. Mais maintenant, il est temps de lier le domaine à SQL Server. J'ai donc commencé à implémenter toute l'infrastructure sur Linq2Sql ... Et quand on a fait une solution simple (inspirée par article de Fredrik Kalseth) et obtenu la première exception, j'ai réalisé toute la tristesse de cette idée ... Voici l'implmentation de IUser:

public partial class USER : IUser 
{ 
    int IHaveID<int>.ID 
    { 
     get { return USERID; } 
    } 

    string IHaveName.Name 
    { 
     get { return USERNAME; } 
     set { USERNAME = value; } 
    } 

    string IUser.FullName 
    { 
     get { return USERFULLNAME; } 
     set { USERFULLNAME = value; } 
    } 

    // ... same approach for other properties 
} 

Et ici - exception:

Exception: System.NotSupportedException: 
    The member 'Data.IHaveName.Name' has no supported translation to SQL. 

Ceci est exception évidente - fournisseur de Linq2Sql ne comprend pas les interfaces externes, mais comment dois-je le résoudre? Je ne peux pas changer les interfaces de domaine pour revenir Linq.Expression comme:

Expression<Func<string>> IHaveName.Name 
{ 
    get { return (() => USERNAME); } 
    set { USERNAME = value(); } 
} 

parce qu'il rompt tout l'utilisation du code actuel des interfaces de domaine et je ne peux d'ailleurs pas remplacer int avec Expression<Func<int>> en raison de croyances religieuses :)

Peut-être, je devrais écrire personnalisée visiteur d'expression pour sauter dans la requête » appel AST de IUser.SomeProperty et le remplacer par ses sous-AST ...

Pouvez-vous fournir vos pensées là-dessus?

MISE À JOUR. Ici, je devrais écrire quelque chose sur la mise en œuvre Repository. Regardez GetAll() source:

public IQueryable<TEntity> GetAll() 
{ 
    ITable table = GetTable(); 
    return table.Cast<TEntity>(); 
} 

protected ITable GetTable() 
{ 
    return _dataContext.GetTable(_implType); 
} 

Dans mon exemple, TEntity <=> IUser et _implType <=> typeof(USER)

MISE À JOUR 2. que j'ai trouvé related question, où OP a un problème aussi bien: besoin d'un certain pont entre les entités ORM en béton et il est entités de domaine. Et j'ai trouvé intéressant the answer: l'auteur a suggéré de créer le visiteur d'expression, qui effectuera la convertion entre les entités (ORM < => Domain).

+0

De nouvelles suggestions? – ajukraine

Répondre

0

Il semble, j'ai trouvé un moyen de résoudre mon problème. Aujourd'hui, j'ai trouvé series of post nommée "Requêtes avancées de modèle de domaine en utilisant Linq". L'auteur présente son moyen de prendre en charge une syntaxe Lambda plus pratique (jolie) pour travailler avec des entités. Et il utilise les conversions Expression Tree. Donc, je vais suivre cette approche, et si je réussis, je vais poster une réponse plus détaillée (peut-être via mon blog).

Que pensez-vous de cette idée? Devrais-je passer du temps à implémenter un fournisseur d'expression?

MISE À JOUR. J'ai échoué avec le vistior Expression personnalisé - il nécessite de nombreuses conversions via l'arbre d'expression. J'ai donc décidé de déplacer toute la logique de Linq dans des classes de "stockage", ce qui englobe l'interrogation complexe et la gestion de la persistance.

0

Décorez les propriétés en Name avec les attributs Column, comme vous le feriez pour tout autre type que vous utilisez avec Linq2SQL.

+0

Voulez-vous dire 'IHaveName' quand vous avez écrit' Name'? – ajukraine

+0

Non, je veux dire Nom, la classe réelle Linq2SQL sera trouver la propriété sur. –

+0

... bien qu'il puisse s'agir d'une propriété dans une classe de base plutôt que d'une interface, qui contourne ce problème dans * certains * cas. –

0

Ceci est un problème bien connu, LINQ-to-SQL en lui-même renvoie cette erreur lorsque vous essayez d'exécuter un IQueryable avec une logique qu'il ne peut pas lancer dans une commande de chaîne SQL.

Ce problème se pose également en raison de la nature de l'exécution IQueryable - différée.

Vous pouvez forcer l'exécution du IQueryable comme suit:

public IUser GetUser(string userName) 
{ 
    return Warehouse.GetRepository<IUser>().GetAll() 
     .AsEnumerable() 
     .First(u => u.Name == userName); 
} 

Cela garantira que vos interfaces de domaine peuvent rester en l'état et ne vous donnera aucun problème lors de l'exécution soit.

À la votre!

+2

Ce n'est pas l'exécution différée de IQueryable qui est le problème (vous auriez aussi différé avec AsEnumerable()), mais le moteur de requête qui fonctionne juste pour l'exécuter est différent. Linq2SQL a besoin d'informations sur la façon de calculer sur la base de données qui n'est pas inhérente à la définition de l'objet. Cela contourne cela, mais nécessite beaucoup, voire la totalité de la table à analyser à chaque demande. –

+0

@JoHanna: Oui, c'est exactement ça. Je travaille beaucoup avec .NET MVC et, en tant que tel, trouve plus simple d'utiliser cette approche et de mettre en cache les tables en question au niveau de l'application avec les dépendances SQL. L'attribut column ne résout pas ce problème, la seule autre alternative que je connaisse est une extension dans IQueryable qui insère le paramètre supplémentaire en tant que champ dans la classe LINQ-to-SQL générée via la base de données. –

+0

Donc, s'il y a 30000 utilisateurs et que l'utilisateur correspondant était le dernier, vous allez récupérer 29999 utilisateurs de la base de données que vous n'utilisez pas? Certes, vous pouvez mettre en cache la table en mémoire et peut-être même par un dictionnaire sur le nom d'utilisateur afin qu'il puisse être récupéré dans un temps presque constant, mais cela entraîne tous les problèmes d'invalidation du cache. –

0

Avez-vous essayé d'utiliser des génériques? Linq to SQL doit connaître le type concret afin qu'il puisse traduire votre expression en sql.

public TUser GetUser<TUser>(string userName) where TUser : IUser 
{ 
    return Warehouse.GetRepository<TUser>().GetAll() 
     .First(u => u.Name == userName); 
} 
+0

Tout d'abord, l'application ne sait rien sur les classes Linq2Sql, donc il est impossible de voir une expression comme 'GetRepository ()' ... – ajukraine

+0

Ensuite, vous devrez faire une sorte de cartographie de votre des classes d'application à vos types Linq2Sql Ce n'est pas joli, je sais, mais des outils comme AutoMapper peuvent rendre la vie plus facile. – jrummell

Questions connexes