当前位置: 动力学知识库 > 问答 > 编程问答 >

c# - Generic Linq query to get array of entities with specified keys

问题描述:

I need a generic query for something like this but couldn't find anything in web (maybe because of bad search!)

int[] ids = new int[]{1,2,3}

var q = context.where(o=> ids.contains(o.id));

in which my entity type is unknown but I'm sure that:

  1. The primary key is single
  2. The primary key type is int
  3. I cannot change the entity definition

So the signature is something like this :

Public IQueryable<T> GetRecords(int[] keys)

{

var dbset = context.Set<T>();

...//the generic query

}

Also it is important to have a same SQL output (to have a same performance) in generic and non-generic implementation.

网友答案:
private IDictionary<Type, string> _primaryKeyCache = new Dictionary<Type, string>();

public IQueryable<T> GetRecords(int[] keys)
{
   var dbSet = context.Set<T>();

   var type = typeof(T);

   string primaryKeyName;
   if(!_primaryKeyCache.TryGetValue(type, out primaryKeyName)){
      var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
      var set = objectContext.CreateObjectSet<YourEntity>();
      var keyName = set.EntitySet.ElementType
                                            .KeyMembers
                                            .Select(k => k.Name)
                                            .First();

      _primaryKeyCache[type] = primaryKeyName = keyName;
   }

   return dbSet.DynamicContains<T, int>(primaryKeyName, keys);
}

private static IQueryable<T> DynamicContains<T, TProperty>(
        this IQueryable<T> query, 
        string property, 
        IEnumerable<TProperty> items)
    {
        var pe = Expression.Parameter(typeof(T));
        var me = Expression.Property(pe, property);
        var ce = Expression.Constant(items); 
        var call = Expression.Call(typeof(Enumerable), "Contains", new[] { me.Type }, ce, me);
        var lambda = Expression.Lambda<Func<T, bool>>(call, pe);
        return query.Where(lambda);
    }

That should do what you need.

网友答案:

You can solve your problem using polymorphism. Crete an interface that declares an integer Id property and implement in your entities, since it is common for all of them. Then have a constraint on the generic parameter T that forces it to be a type that implements your interface, so the compiler will know that the T will be a type that has Id property and you will be able to access it:

// IId is the interface that should be implemented
public IQueryable<T> GetRecords<T>(int[] keys) where T : IId
{
   var dbset = context.Set<T>();
    ...//the generic query
}
网友答案:

Create an new interface, e.g. IKeyedByIntEntity

public interface IKeyedByIntEntity
{
  int Key { get; }
}

Have all entities to be used in the query implement the interface. In an ideal world, the field has the same name so you can use that as the property name in the interface and thus not have to change the implementation of your entities.

public class MyEntity : IKeyedByIntEntity
{
  public int Key { get; set; }
  public string Value { get; set; }
}

Update your GetRecords function or class to require the interface, something like one of these...

public class Fetch
{
  public IQueryable<T> GetRecords<T>(int[] keys)
    where T : IKeyedByIntEntity
  {
    IQueryable<T> records = this.context.Where(e => keys.Contains(e.Key));
    return records;
  }
}

public class Fetch<T> where T : IKeyedByIntEntity
{
  public IQueryable<T> GetRecords(int[] keys)
  {
    IQueryable<T> records = this.context.Where(e => keys.Contains(e.Key));
    return records;
  }
}
分享给朋友:
您可能感兴趣的文章:
随机阅读: