Pergunta

Problema: eu gostaria de compartilhar código entre várias montagens. Esse código compartilhado precisará trabalhar com as classes LINQ para mapeadas do SQL.

Eu encontrei o mesmo problema encontrado aqui, mas também achei um trabalho que acho preocupante (não vou ao ponto de dizer "bug").

Todo o código a seguir pode ser baixado em esta solução.

Dada esta tabela:

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

Eu criei as seguintes classes base em uma assembléia "compartilhada":

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

Essas classes são referenciadas em outra assembléia "Main", assim:

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

    }
}

O arquivo DBML também está localizado na montagem "principal".

Ao ligar User.FindByName_Broken(db, "test"), uma exceção é lançada:

System.InValidoPoperationException: Classe Membro UserBase.Name é não mapeado.

No entanto, os outros dois métodos estáticos básicos funcionam.

Além disso, o SQL gerado ligando User.FindByName_Works(db, "test") é o que esperávamos na chamada quebrada:

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]

Enquanto estou disposto a usar isso 1 == 1 "Hack" para consultas de predicado único, existe uma maneira melhor de compartilhar o código LINQ para SQL-Aware em uma montagem base/compartilhada/núcleo?

Foi útil?

Solução

Eu encontrei esse problema muitas vezes no passado porque temos uma arquitetura semelhante em uma estrutura que usamos em nossa empresa. Você deve ter notado que, se usar as consultas de LINQ de estilo declarativo, não encontrará esse problema. Por exemplo, o código a seguir funcionará:

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

No entanto, como estamos usando expressões de filtro dinâmico, não poderíamos usar esse método. A solução alternativa é usar algo assim:

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

Esta solução resolveu nosso problema, pois podemos injetar um ".Select (i => i)" para o início de quase todas as expressões. Isso fará com que o mecanismo LINQ não observe a classe base para os mapeamentos e o forçará a olhar para a classe de entidade real e encontrar os mapeamentos.

Espero que ajude

Outras dicas

Tente incluir o ofttype antes de onde a cláusula

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

Tive a sorte de definir as classes de dados em uma montagem compartilhada e consumi -las em muitas assembléias, em vez do mapeamento das classes de dados de muitas assembléias para um contrato compartilhado. Usando seus espaços de nome de exemplo, coloque um datacontext personalizado e suas classes de dados compartilhadas em 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; }
    }
}

Em seguida, consuma o DataContext de qualquer outra montagem:

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

Parece um bug - nós, um caso especial, em uma chave primária para fazer uma pesquisa local, mas parece que esse caminho de código não está agarrando os metadados corretamente.

O hack 1 = 1 significa que passa por uma ida e volta normal de banco de dados, mas realmente um bug deve ser arquivado ...

Você está fazendo várias perguntas aqui Jarrod, você pode ser mais específico? Ou seja, você só quer saber por que seu método falha? Ou talvez você queira uma maneira de usar objetos de dados em diferentes projetos? Suponho que você não esteja tentando usar o LINQ para SQL como uma camada de mapeamento de banco de dados e que o está usando como um modelo de domínio? Nesse caso, ambos os aplicativos implementam o mesmo domínio (processos de negócios, validação etc.)?

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top