2009-06-20 8 views
21

Problem: Ich möchte Code zwischen mehreren Baugruppen freigeben. Dieser gemeinsam genutzte Code muss mit LINQ zu SQL-Map-Klassen funktionieren.LINQ to SQL - Zuordnungsausnahme bei Verwendung von abstrakten Basisklassen

Ich habe das gleiche Problem gefunden gefunden here, aber ich habe auch eine Work-Around gefunden, die ich beunruhigend finde (ich gehe nicht so weit zu sagen "Bug").

Der gesamte folgende Code kann in this solution heruntergeladen werden.

Angesichts dieser Tabelle:

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 
) 

und diese DBML Mapping:

<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> 

ich die folgenden Basisklassen in einer Baugruppe "Shared" erstellt haben:

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); 
     } 
    } 
} 

Diese Klassen werden in einer anderen Assembly "Main" wie folgt referenziert:

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

    } 
} 

Die DBML-Datei befindet sich auch in der "Main" -Assembly.

System.InvalidOperationException: Klasse Mitglied UserBase.Name ist unmapped

Wenn User.FindByName_Broken(db, "test") Aufruf, wird eine Ausnahme ausgelöst.

Die anderen beiden statischen Basismethoden funktionieren jedoch.

Darüber hinaus ist die SQL erzeugt durch User.FindByName_Works(db, "test") Aufruf wird, was in dem gebrochenen Ruf der Hoffnung, wir waren:

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] 

Während ich bin bereit, dies für einzelne Prädikat Abfragen 1 == 1 „Hack“ zu verwenden, gibt es ein besseres Möglichkeit, LINQ mit SQL-fähigem Code in einer base/shared/core Assembly zu teilen?

Antwort

19

ich dieses Problem oft begegnet in die Vergangenheit, weil wir eine ähnliche Architektur in einem Rahmen haben, den wir in unserer Firma verwenden.Sie haben möglicherweise bemerkt, dass bei Verwendung der deklarativen LINQ-Abfragen dieses Problem nicht auftritt. Der folgende Code wird beispielsweise funktionieren:

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

Da wir jedoch dynamische Filterausdrücke verwenden, können wir diese Methode nicht verwenden. Die alternative Lösung ist so etwas wie diese verwenden:

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

Diese Lösung unser Problem gelöst, da wir ein „.Mit (i => i)“ injizieren kann zu Anfang fast alle Ausdrücke. Dies führt dazu, dass die Linq-Engine die Basisklasse für die Zuordnungen nicht betrachtet und sie dazu zwingt, die tatsächliche Entitätsklasse zu betrachten und die Zuordnungen zu finden.

Hoffen, dass es

+1

Es macht eine Abfrage ohne where-Klausel und dann wird die Liste im Speicher gefiltert. Nicht gut für viele Ergebnisse; – Gandarez

0

Sie stellen hier einige Fragen Jarrod, können Sie genauer sein? Willst du nur wissen, warum deine Methode fehlschlägt? Oder möchten Sie vielleicht eine Möglichkeit, Datenobjekte in verschiedenen Projekten zu verwenden? Ich nehme an, dass Sie nicht versuchen, LINQ to SQL als Datenbank-Mapping-Layer zu verwenden, und dass Sie es als Domänenmodell verwenden? In welchem ​​Fall implementieren beide Anwendungen dieselbe Domäne (Geschäftsprozesse, Validierung usw.)?

3

Ich hatte Glück, Datenklassen in einer freigegebenen Assembly zu definieren und sie in vielen Assemblies zu verbrauchen, anstatt die Datenklassen vieler Assemblys einem gemeinsamen Vertrag zuzuordnen. Mit Ihrem Beispiel Namespaces, setzen Sie eine benutzerdefinierte Datacontext und Ihre freigegebenen Datenklassen 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; } 
    } 
} 

Dann verbrauchen die Datacontext von einer anderen Baugruppe:

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

hilft diese wie ein Fehler aussieht - wir Sonderfall Einzel auf einem Primärschlüssel eine lokale Lookup zu tun, aber es sieht aus wie dieser Codepfad die Metadaten nicht richtig ist grabbing.

Die 1 = 1 Hack bedeutet, es geht über eine normale Datenbank Round-Trip aber eigentlich sollte ein Fehler abgelegt werden ...

+1

Eigentlich sieht es aus, als ob bereits ein Fehlerbericht existiert - mit dem Status "Closed, Will not Fix". :( http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=394255 – daveidmx

5

Versuchen einschließlich OfType vor Where-Klausel

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

+0

Wow, die einzige Lösung, die für mich funktioniert, vielen Dank für das Posten. – ViRuSTriNiTy