2

I have the following code:

public interface IKeyed<TKey>
{
    TKey Id { get; }
}

// This is the entity framework generated model. I have added the
//    IKeyed<Guid> interface
public partial class Person : IKeyed<Guid>
{
    public Guid Id { get; set; }
}

public class Repository<TKey, TEntity> : IKeyedRepository<TKey, TEntity>
               where TEntity : class, IKeyed<TKey>
{
    private readonly IObjectSet<TEntity> _objectSet;

    public Repository(IOjectSet<TEntity> objectSet)
    {
        _objectSet = objectSet;
    }

    public TEntity FindBy(TKey id)
    {
         return _objectSet.FirstOrDefault(x => x.Id.Equals(id));
    }
}

[Update] Here is how I am calling this:

Db2Entities context = new Db2Entities(_connectionString); // This is the EF context
IObjectSet<Person> objectSet = context.CreateObjectSet<Person>();

IKeyedRepository<Guid, Person> repo = new Repository<Guid, Person>(objectSet);

Guid id = Guid.NewGuid();
Person person = repo.FindBy(id);   // This throws the exception.

The above code compiles. When the 'FindBy' method is executed, I get the following error:

Unable to create a constant value of type 'Closure type'. Only primitive types (for instance Int32, String and Guid) are supported in this context.

Since the type of my 'Id' is a Guid (one of the primitive types supported) it seems like I should be able to massage this into working.

Anyone know if this is possible?

Thanks,

Bob

Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
rcravens
  • 8,320
  • 2
  • 33
  • 26
  • 1
    If I have to judge by my adventures into writing linq providers to stuff, I assume EF's provider parses the expression given to first or default by type. And type of id is TKey which is NOT typeof(Guid). – TDaver May 20 '11 at 20:48
  • I believe TDaver is right. The code actually works if you would have a non-generic parameter `Guid` instead the the generic `TKey` parameter. – Slauma May 21 '11 at 12:24

1 Answers1

3

It doesn't work this way. You cannot call Equals because EF doesn't know how to translate it to SQL. When you pass expression to FirstOrDefault it must be always only code which can be translated to SQL. It is probably possible to solve your problem with some manual building of expression tree but I can reference other solutions already discussed on Stack Overflow.

ObjectContext offers method named GetObjectByKey which is exactly what you are trying to do. The problem is that it requires EntityKey as parameter. Here are two answers which show how to use this method and how to get EntityKey:

In your case the code will be less complicated because you know the name of the key property so you generally need only something like this:

public virtual TEntity FindBy(TKey id)
{
    // Build entity key
    var entityKey = new EntityKey(_entitySetName, "Id", key);
    // Query first current state manager and if entity is not found query database!!!
    return (TEntity)Context.GetObjectByKey(entityKey);
}

The problem here is that you cannot get entitySetName from IObjectSet so you must either pass it to repository constructor or you must pass ObjectSet.

Just in case you will want to use DbContext API (EFv4.1) in the future instead of ObjectContext API it will be much simplified because DbSet offers Find method:

Community
  • 1
  • 1
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • I believe `Equals` isn't the problem. Usually it is allowed in LTE and translates to SQL. I think the problem falls under the category of "referencing a non-scalar variable" in a LTE query (the generic TKey parameter) which causes the exception (http://msdn.microsoft.com/en-us/library/bb896317.aspx) – Slauma May 21 '11 at 12:29
  • I was able to implement using @Ladislav advice. If you are interested in seeing the final result it is here: https://github.com/rcravens/GenericRepository – rcravens May 21 '11 at 13:47