Usando a reflection para endereçar uma propriedade Linqed

Estou tentando escrever um método genérico que carregará um registro de um tipo específico, com um ID específico. Aqui está uma maneira que funciona:

public abstract class LinqedTable where T : LinqableTable { public static T Get(long ID) { DataContext context = LinqUtils.GetDataContext(); var q = from obj in context.GetTable() where obj.ID == ID select obj; return q.Single(); } } public abstract class LinqableTable { public abstract long ID { get; set; } } 

Você pode ignorar a chamada para LinqUtils.GetDataContext() ; Essa é uma function de utilidade que eu tenho que lidar com o fato de que eu tenho vários contextos de dados no meu programa. O ponto é que agora posso declarar qualquer uma das minhas classs como subclasss de LinqableTable , e posso facilmente instanciar um registro dessa tabela apenas chamando LinqedTable.Get(ID) .

Isso tem algumas limitações, no entanto. Em primeiro lugar, força todas as minhas tabelas a ter um campo de identidade do tipo long , chamado ID . Em segundo lugar, como estou usando um método abstrato, sou forçado a ir para o designer de O / R e alterar a propriedade de inheritance de cada campo de ID no meu sistema para “replace”.

Eu quero mais flexibilidade do que isso. Então, naturalmente, eu tentei refletir e saí com o seguinte:

  public abstract class LinqedTable where T : LinqableTable { public static T Get(long ID) { DataContext context = LinqUtils.GetDataContext(); var q = from obj in context.GetTable() where obj.IDValue == ID select obj; return q.Single(); } } public abstract class LinqableTable { internal long IDValue { get { return (long)IDProperty.GetValue(this, null); } set { IDProperty.SetValue(this, value, null); } } internal PropertyInfo IDProperty { get { return this.GetType().GetProperty(IDPropertyName); } } internal protected virtual string IDPropertyName { get { return "ID"; } } } 

Teoricamente, isso me permite replace o nome da coluna de ID, a conversão para long deve ser OK com qualquer tipo de dados integral e não preciso definir todas as minhas colunas de ID como overrides .

MAS

Linq não gosta disso. Na chamada para q.Single(); Eu recebo um erro de tempo de execução:

The member 'EISS.Utils.LinqableTable.IDValue' has no supported translation to SQL.

OK, hoje eu aprendi que o Linq faz algum tipo de mágica no final; ele não instancia o obj e apenas lê a propriedade IDValue . Então deve haver algum atributo que precisa ser definido na propriedade IDValue que permite que o Linq faça o seu trabalho.

Mas o que?

Linq to SQL tenta traduzir sua consulta linq para SQL, mas não sabe como traduzir sua propriedade para um nome de coluna no database.

Uma boa explicação pode ser encontrada aqui no SO: simples linq para sql não tem tradução suportada para SQL

Mas como resolver isso, é outro assunto. Eu tenho com sucesso usado o apporoach deste segmento:

http://social.msdn.microsoft.com/forums/pt-BR/linqprojectgeneral/thread/df9dba6e-4615-478d-9d8a-9fd80c941ea2/

Ou você pode usar a consulta dinâmica como mencionado aqui por scott guthrie:

http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

Depois de ler esses posts: Acesso a dados genéricos usando LINQ to SQL e C # , LINQ para SQL: function de chave primária genérica e chamando um método genérico com tipo

Meu colega e eu apresentamos o seguinte resumo:

Adicionamos o seguinte método ao nosso datacontext (em uma class parcial).

 public T GetInstanceByPrimaryKey(object primaryKeyValue) where T : class { var table = this.GetTable(); var mapping = this.Mapping.GetTable(typeof(T)); var pkfield = mapping.RowType.DataMembers.SingleOrDefault(d => d.IsPrimaryKey); if (pkfield == null) throw new Exception(String.Format("Table {0} does not contain a Primary Key field", mapping.TableName)); var param = Expression.Parameter(typeof(T), "e"); var predicate = Expression.Lambda>(Expression.Equal(Expression.Property(param, pkfield.Name), Expression.Constant(primaryKeyValue)), param); return table.SingleOrDefault(predicate); } 

Então, onde precisamos instanciar do nome do tipo e do valor da chave primária:

 string name = "LinqObjectName"; int primaryKey = 123; var dc = new YourDataContext(); Type dcType = dc.GetType(); Type type = dcType.Assembly.GetType(String.Format("{0}.{1}", dcType.Namespace, name)); MethodInfo methodInfoOfMethodToExcute = dc.GetType().GetMethod("GetInstanceByPrimaryKey"); MethodInfo methodInfoOfTypeToGet = methodInfoOfMethodToExcute.MakeGenericMethod(name); var instance = methodInfoOfTypeToGet.Invoke(dc, new object[] { primaryKey }); return instance; 

Espero que isto ajude!

Como as instruções LINQ referenciadas a um IQQuery de LINQ para SQL são traduzidas para consultas SQL, você terá que usar a extensão AsEnumerable (que por sua vez causará uma leitura de todos os itens no database) e fazer coisas relacionadas à reflection IEnumerable.
EDITAR
Conforme necessário, aqui está um esclarecimento
Como especificado em um comentário, o que eu quis dizer foi algo como:

  (from obj in context.GetTable() select obj).AsEnumerable().Where(x => x.IDValue == ID) 

Ao contrário de uma consulta executada em um IQueryable, que pode ser traduzida perfeitamente para o SQL, como

 context.GetTable().Where(x => x.Text == "Hello") 

que é convertido em algo semelhante a

 SELECT * FROM TABLE_MAPPED_TO_TYPE_T WHERE Text = 'Hello' 

uma consulta executada em um IEnumerable – no seu caso – será executada buscando todas as inputs da tabela e aplicando o filtro especificado em código.