3

I am writing a generic repository to interface with EF using DBContext.

I have a generic Get() method which receives a primary key value and returns the entity:

public class DALRepository<DALEntity> : IDisposable, IGenericRepository<DALEntity> where DALEntity : class
{
private IDbSet<DALEntity> dbSet;
private NWEntities context;

public DALRepository()
{
  context = new NWEntities();
  context.Configuration.LazyLoadingEnabled = false;
  dbSet = context.Set<DALEntity>();
}

Here's a simple get method - just works on the PK - exactly what I want.

public DALEntity Get(string ID)
{
   return dbSet.Find(ID);
}

I now want to change this to allow the consumer to pass in a list of includes - so as well as returning just a customer they can request to return the orders as well. Here's where I'm running into trouble. If I do this:

public DALEntity Get(string ID, IEnumerable<string> IncludeEntities = null)
{
      IQueryable<DALEntity> query = dbSet;
      query = IncludeEntities.Aggregate(query, (current, includePath) => current.Include(includePath));
}

I can't use find with the IQueryable. And I can't Find() directly because I can't pass includes to it. If I use a where lambda instead on the IQueryable, how do I tell it to use the PK of the entity? I guess I could have a generic constraint that insists the generic type must implement some IPkey interface with a well-defined primary column name such as "ID", but then I can't use the entities generated by DBContext as they woudl have to implement this interface. I can change the T4 to do this if I need - and I've already changed it to emit XML comments so not too averse to that - but does anyone have a simpler way? I suppose what I need is an overloaded find() which accepts a list of includes.

So my question is either how to use Find with includes, or how to write the lambda where it knows the PK? I can't receive such a lambda as a parameter as this will ultimately be consumed by a WCF service.

rikitikitik
  • 2,414
  • 2
  • 26
  • 37
RBrowning99
  • 411
  • 2
  • 9
  • 21

3 Answers3

2

Kind of wierd answering your own question but in case anyone else has this issue here's what I did. I used the dynamic LINQ stuff and used the string overloaded version of .Where(). I used one of the links mentioned to figure out how to grab the primary key (and mine is from a DBContext as well), and the method now looks like this:

public DALEntity Get(string ID, IEnumerable<string> IncludeEntities = null)
{
  var set = ((IObjectContextAdapter)context).ObjectContext.CreateObjectSet<DALEntity>();
  var entitySet = set.EntitySet;
  string[] keyNames = entitySet.ElementType.KeyMembers.Select(k => k.Name).ToArray();
  Debug.Assert(keyNames.Length == 1, "DAL does not work with composite primary keys or tables without primary keys");

  IQueryable<DALEntity> query = dbSet;
  query = IncludeEntities.Aggregate(query, (current, includePath) => current.Include(includePath));

  query = query.Where(keyNames[0] + "= @0", ID);
  return query.FirstOrDefault();
}
RBrowning99
  • 411
  • 2
  • 9
  • 21
0

You could get your Key dinamycally the way it is described here : https://stackoverflow.com/a/10796471/971693

That being said, I can't see a way without using some reflection :

public IEnumerable<DALEntity> Get(params string IDs)
{
   var objectSet = objectContext.CreateObjectSet<YourEntityType>();
   var keyNames = objectSet.EntitySet.ElementType.KeyMembers.First(k => k.Name);

   return dbSet.Where(m => ID.Contains((string)m.GetType().GetProperty(keyNames ).GetValue(m, null));
}

First of, params string IDs will let you pass 1 or more ID and will result in an array of string. The first part of the function is to dynamically get the name of your primary key.

The second part creates a query to return all elements from your set where the primary key value (obtained through reflection) is contained within the array of IDs received in parameter.

Community
  • 1
  • 1
Yan Brunet
  • 4,727
  • 2
  • 25
  • 35
  • Well I'm the first admit I don't understand this - have to think on that one for a bit! Will respond once I've got my head around it...how would that work where the ID is an integer (I have two overloaded Gets()) – RBrowning99 Oct 25 '12 at 01:20
  • I've added some explanation to my answer. And for the overloaded Gets, just replace string with int and it will work in the same way. – Yan Brunet Oct 25 '12 at 09:39
  • 1
    thanks for this - I decided not to use it in the end but I did use your way of getting the PK (after I figured out how to get to it from DBContext). I went with dynamic LINQ in the end - very simple. – RBrowning99 Oct 26 '12 at 02:54
  • Thanks for the feedback. I never had to use dynamic linq thus I didn't think of this option. That is certanly more readable than my reflection approach. – Yan Brunet Oct 26 '12 at 11:35
  • I haven't used it before, either, but it's worked out very well. Another I needed to do was some way of passing through a filter string from a front end through WCF to the BLL/DAL. Obviously I can't send lambas throught the WCF so I pass these strings. best Ray – RBrowning99 Oct 26 '12 at 18:39
0

Use Linq's Single or First methods, which allow you to search on IQueryable objects.

public DALEntity Get(string ID, IEnumerable<string> IncludeEntities = null)
{
      IQueryable<DALEntity> query = dbSet;
      query = IncludeEntities.Aggregate(query, (current, includePath) => current.Include(includePath));
      query = query.Single(x=>x.Id == ID);
}
Ryan Amies
  • 4,902
  • 1
  • 21
  • 36
  • Hi and thanks for the input. I can't use what you suggested - I can't have been clear enough in my initial question - because I don't know what the primary field name is. I suppose your solution would work if we had a generic constraint which said must implement a interface which defines a property called Id? Otherwise that lamda you have - even if all classes did have a field called Id - wouldn't work woud it unless the compiler knew that must have a property Id. Unless I'm missing something. – RBrowning99 Oct 26 '12 at 02:59