2009-06-20 7 views
21

Problema: desidero condividere il codice tra più assiemi. Questo codice condiviso dovrà funzionare con LINQ per classi mappate SQL.LINQ to SQL: associazione dell'eccezione quando si utilizzano le classi di base astratte

Ho riscontrato lo stesso problema trovato here, ma ho trovato anche un work-around che trovo problematico (non ho intenzione di dire "bug").

Tutto il codice seguente può essere scaricato in this solution.

Data questa tabella:

create table Users 
(
     Id int identity(1,1) not null constraint PK_Users primary key 
    , Name nvarchar(40) not null 
    , Email nvarchar(100) not null 
) 

e questa mappatura DBML:

<Table Name="dbo.Users" Member="Users"> 
    <Type Name="User"> 
    <Column Name="Id" Modifier="Override" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" /> 
    <Column Name="Name" Modifier="Override" Type="System.String" DbType="NVarChar(40) NOT NULL" CanBeNull="false" /> 
    <Column Name="Email" Modifier="Override" Type="System.String" DbType="NVarChar(100) NOT NULL" CanBeNull="false" /> 
    </Type> 
</Table> 

Ho creato le seguenti classi di base in un'assemblea "in comune":

namespace TestLinq2Sql.Shared 
{ 
    public abstract class UserBase 
    { 
     public abstract int Id { get; set; } 
     public abstract string Name { get; set; } 
     public abstract string Email { get; set; } 
    } 

    public abstract class UserBase<TUser> : UserBase where TUser : UserBase 
    { 
     public static TUser FindByName_Broken(DataContext db, string name) 
     { 
      return db.GetTable<TUser>().FirstOrDefault(u => u.Name == name); 
     } 

     public static TUser FindByName_Works(DataContext db, string name) 
     { 
      return db.GetTable<TUser>().FirstOrDefault(u => u.Name == name && 1 == 1); 
     } 

     public static TUser FindByNameEmail_Works(DataContext db, string name, string email) 
     { 
      return db.GetTable<TUser>().FirstOrDefault(u => u.Name == name || u.Email == email); 
     } 
    } 
} 

Queste classi sono referenziate in un altro assembly "Main", in questo modo:

namespace TestLinq2Sql 
{ 
    partial class User : TestLinq2Sql.Shared.UserBase<User> 
    { 

    } 
} 

Il file DBML si trova anche nell'assieme "Principale".

Quando si chiama User.FindByName_Broken(db, "test"), viene generata un'eccezione:

System.InvalidOperationException: UserBase.Name membro della classe è mappata.

Tuttavia, gli altri due metodi statici di base funzionano.

Inoltre, l'SQL generato chiamando User.FindByName_Works(db, "test") è ciò che speravamo nella chiamata rotta:

SELECT TOP (1) [t0].[Id], [t0].[Name], [t0].[Email] 
FROM [dbo].[Users] AS [t0] 
WHERE [t0].[Name] = @p0 
-- @p0: Input NVarChar (Size = 4; Prec = 0; Scale = 0) [test] 

Mentre io sono disposto a utilizzare questo 1 == 1 "hack" per le query singole predicato, c'è una migliore modo di condividere LINQ con codice SQL-aware in un assembly base/condiviso/core?

risposta

19

ho riscontrato questo problema molte volte in il passato perché abbiamo un'architettura simile in un quadro che usiamo nella nostra azienda.Potresti aver notato che se usi le query LINQ in stile dichiarativo non incontrerai questo problema. Ad esempio, il seguente codice funzionerà:

return (from i in db.GetTable<TUser>() where i.Name = "Something").FirstOrDefault(); 

Tuttavia, poiché si utilizzano espressioni di filtro dinamiche, non è possibile utilizzare questo metodo. La soluzione alternativa è quella di utilizzare qualcosa di simile:

return db.GetTable<TUser>().Select(i => i).Where(i => i.Name == "Something").SingleOrDefault(); 

Questa soluzione ha risolto il nostro problema in quanto siamo in grado di iniettare un ".Selezionare (i => i)" per l'inizio di quasi tutte le espressioni. Questo farà sì che il motore Linq non guardi la classe base per i mapping e lo costringerà a guardare la classe di entità effettiva e trovare i mapping.

Speranza che aiuta

+1

Rende una query senza clausola where e quindi l'elenco viene filtrato in memoria. Non va bene per molti risultati; – Gandarez

0

Stai facendo diverse domande qui Jarrod, puoi essere più specifico? Cioè, vuoi solo sapere perché il tuo metodo fallisce? O forse vuoi un modo di usare gli oggetti dati tra diversi progetti? Suppongo che tu non stia cercando di usare LINQ to SQL come livello di mappatura del database e che lo stai usando come modello di dominio? In tal caso, entrambe le applicazioni implementano lo stesso dominio (processi aziendali, convalida, ecc.)?

3

Ho avuto la fortuna di definire le classi di dati in un assembly condiviso e di consumarle in molti assembly rispetto alla mappatura di classi di dati di molti assembly in un contratto condiviso. Usando il tuo esempio spazi dei nomi, mettere un DataContext costume e le vostre classi di dati condivisi in TestLinq2Sql.Shared:

namespace TestLinq2Sql.Shared 
{ 
    public class SharedContext : DataContext 
    { 
     public Table<User> Users; 
     public SharedContext (string connectionString) : base(connectionString) { } 
    } 

    [Table(Name = "Users")] 
    public class User 
    { 
     [Column(DbType = "Int NOT NULL IDENTITY", IsPrimaryKey=true, CanBeNull = false)] 
     public int Id { get; set; } 

     [Column(DbType = "nvarchar(40)", CanBeNull = false)] 
     public string Name { get; set; } 

     [Column(DbType = "nvarchar(100)", CanBeNull = false)] 
     public string Email { get; set; } 
    } 
} 

quindi consumare il DataContext da qualsiasi altro gruppo:

using (TestLinq2Sql.Shared.SharedContext shared = 
    new TestLinq2Sql.Shared.SharedContext(
     ConfigurationManager.ConnectionStrings["myConnString"].ConnectionString)) 
{ 
    var user = shared.Users.FirstOrDefault(u => u.Name == "test"); 
} 
3

Questo appare come un bug - abbiamo caso speciale singolo su una chiave primaria per fare una ricerca locale, ma sembra che questo percorso di codice non è catturare i metadati in modo corretto.

Il 1 = 1 mod vorrà dire che va via un database normale di andata e ritorno, ma in realtà un bug dovrebbe essere depositata ...

+1

In realtà, sembra che esista già una segnalazione di bug - con stato "Chiuso, Non risolve". :( http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=394255 – daveidmx

5

Prova compresi OfType prima clausola Where

return _dbContext.GetTable<T>().OfType<T>().Where(expression).ToList();

+0

Wow, l'unica soluzione che funziona per me, grazie mille per aver postato questo. – ViRuSTriNiTy