12

Calling Get in the following code works fine:

public class ContractService : IContractService
{
    private readonly IRepository<Contract> repository;

    public ContractService(IRepository<Contract> repository)
    {
        this.repository = repository;
    }

    public Contract Get(int contractId)
    {
        return repository.Query().Where(x => x.Id == contractId).FirstOrDefault();
    }

but when i do this:

public class ContractService : CRUDService<Contract>, IContractService
{
    public ContractService(IRepository<Contract> repository) : base(repository)
    {
    }
}


public class CRUDService<TEntity> : ICRUDService<TEntity> where TEntity : IEntity
{
    protected readonly IRepository<TEntity> repository;

    public CRUDService(IRepository<TEntity> repository)
    {
        this.repository = repository;
    }

    public TEntity Get(int id)
    {
        var entities = this.repository.Query().Where(s => s.Id == id);
        return entities.FirstOrDefault();
    }

"entities" inside the get method throws an exception when you iterate over it:

Invalid cast from 'System.Int32' to 'TEntity' (where TEntity is the type name)

Anyone got any idea why?

Edit: here's what the different expressions look like:

In the generic version (top one), it seems to be trying to convert x for some reason, which must be because of the generics :s

{value(NHibernate.Linq.Query`1[Contract]).Where(x => (Convert(x).Id = value(CRUDService`1+<>c__DisplayClass0[Contract]).Id)).FirstOrDefault()}

{value(NHibernate.Linq.Query`1[Contract]).Where(x => (x.Id = value(ContractService+<>c__DisplayClass2).Id)).FirstOrDefault()}

(namespaces omitted for clarity)

2nd Edit: It seems to be when it tries to convert between IEntity and the instance type (TEntity)

here is IEntity:

public interface IEntity
{
    int Id { get; }
}

3rd Edit: it seems to be the Convert(x) that causes the AssociationVisitor to not properly visit the expression tree and convert "Convert(x).Id"

4th Edit: And there we go, someones already found the bug https://nhibernate.jira.com/browse/NHLQ-11!

Thanks

Andrew

Daniel Schilling
  • 4,829
  • 28
  • 60
Andrew Bullock
  • 36,616
  • 34
  • 155
  • 231
  • That does sound very strange. Have you got any logs of what SQL is being executed? What does the rest of the stack trace look like? – Jon Skeet Mar 10 '09 at 10:02
  • No SQL appears to be run, looks like the Linq fails before it gets anywhere near the DB. i've just downloaded the source for a debug http://sourceforge.net/project/showfiles.php?group_id=216446&package_id=306405&release_id=654054 – Andrew Bullock Mar 10 '09 at 10:19
  • I have also ran into this problem! – bleevo May 21 '09 at 06:47
  • Linq to NHibernate is currently being rewritten, im working around this until its released. – Andrew Bullock May 22 '09 at 11:58

4 Answers4

3

I believe the problem is that Linq/NHibernate is trying to map IEntity.Id to a table column instead of TEntity.Id. I had this problem with a LinqToSql repository implementation. The way around it was to use an expression like this:

private static Expression<Func<TEntity, bool>> GetFindExpression(string propertyName, object value)
{
    ParameterExpression parameterExpression = Expression.Parameter(typeof (TEntity), "id");
    MemberExpression propertyExpression = Expression.Property(parameterExpression, propertyName);

    Expression bodyExpression = Expression.Equal(propertyExpression, Expression.Constant(value));

    return Expression.Lambda<Func<TEntity, bool>>(bodyExpression, parameterExpression);
}

So this would change Get(id) to:

public TEntity Get(int id)
{
    var entities = Query.Where(GetFindExpression("Id", id));
    return entities.FirstOrDefault();
}

Update:

If you don't want to deal with expressions (they can be tricky!), you could use the Dynamic LINQ library as described by Scott Guthrie here: http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

That would change Get(id) to:

public TEntity Get(int id)
{
    var entities = Query.Where("Id = @0", id);
    return entities.FirstOrDefault();
}
jrummell
  • 42,637
  • 17
  • 112
  • 171
  • 1
    "I believe the problem is that Linq/NHibernate is trying to map IEntity.Id to a table column instead of TEntity.Id." I totally agree. I like the idea of transforming the lambda, I'm going to try and make a generic version which will handle all transforms... stay tuned – Andrew Bullock Mar 20 '09 at 10:19
1

The bug has been reported but not yet fixed:

https://nhibernate.jira.com/browse/NHLQ-11

I have posted a simple test case to reproduce it.

Let's hope it get's addressed soon!

Daniel Schilling
  • 4,829
  • 28
  • 60
Chris Haines
  • 6,445
  • 5
  • 49
  • 62
1

I've ran into this in the past and it usually boils down to the field in the database table you're reading is not in a compatible format. Like booleans don't convert to integers.

Check your field types in the table and make sure they are compatible. Maybe your Id column is a BIGINT?

Sophtware
  • 1,796
  • 2
  • 21
  • 34
  • i agree that could cause an "invalid cast" exception, but thats not whats going on here. Read the code and the bug report on jira. Its a bug caused by the generics – Andrew Bullock Mar 19 '09 at 11:22
1

I am having the same problem :(

The following does not work.

public override T Load(int id)
{
    return (from t in _sessionFactory.Session.Linq<T>()
            where t.ID == id 
            select t).SingleOrDefault();
}

The following does!

public override Product Load(int id)
{
    return (from t in _sessionFactory.Session.Linq<Product>()
            where t.ID == id
            select t).SingleOrDefault();
}
bleevo
  • 1,637
  • 2
  • 18
  • 30