In a standard class if i wanted to include ProductImages table for all the products as part f my query, i could write something like
from prods in dataContext.Products.Include(i => i.ProductImages) select prods;
I now want to turn this into a generic method so no matter what source it is, it would be able to include the table i require. I started off with a generic method (leaving out the class declaration for brevity T is declared as part of the class i.e. Public Class<T>):
public IEnumerable<T> GetAll()
{
return _entities;
}
This works with no matter what source i have whether it be a product table or customer table i can pass in the table and it returns that object (so T can be a Product or Customer). I now want to introduce the Include functionality.
I start off by looking at the source code for Include which is
public static IQueryable<T> Include<T, TProperty>(this IQueryable<T> source, Expression<Func<T, TProperty>> path)
{
Check.NotNull(source, "source");
Check.NotNull(path, "path");
if (!DbHelpers.TryParsePath(path.Body, out var path2) || path2 == null)
{
throw new ArgumentException(Strings.DbExtensions_InvalidIncludePathExpression, "path");
}
return source.Include(path2);
}
however i noticed it has an additional TProperty which i dont know where or how i should implement this into my generic method?
I attempted to do the following based on the source
IEnumerable<T> GetAll(Expression<Func<T>> path);
but again i dont know how to configure/introduce the TProperty (or equivalent) in this fashion so it works like my first example?
Related
I'd like to write an overload for Includes that would allow me to do something like db.Transactions.Include(t => t.Customer, t => t.Order) rather than needing to do db.Transactions.Include(t => t.Customer).Include(t => t.Order). Here's my attempt:
public static IQueryable<T> Include<T, TProperty>(this IQueryable<T> source, params Expression<Func<T, TProperty>>[] paths) {
foreach (var path in paths) {
source = QueryableExtensions.Include(source, path);
}
return source;
}
But when I try to use it, I get an error that says "The type arguments for the method cannot be inferred from the usage." Is what I'm trying to do possible?
Your method won't work as you are expecting it to. Currently, your method will expect that every expression is returning the same type, which will not work if you pass selectors for class properties of different type.
To properly handle various types, you need to change the signature of the method by removing type parameter TProperty and using Expression<Func<T, object>> as argument type.
Resulting signature should be as follows:
public static IQueryable<T> Include<T>(this IQueryable<T> source, params Expression<Func<T, object>>[] paths)
This will allow you to pass collection of various expressions to the method.
You may suspect, that it will cause problems, because you are implicitly converting the property to object, but it won't be an issue. If you examine the source code of QueryableExtensions.Include (and moving deeper to DbHelpers.TryParsePath), you will see that conversion is removed before expression is converted to string.
Yesterday Ognyan helped me a great deal write this method:
public static class DbSetExtensions
{
public static T AddIfNotExists<T>(this DbSet<T> dbSet, T entity, Expression<Func<T, bool>> predicate = null) where T : class, new()
{
var exists = predicate != null ? dbSet.Any(predicate) : dbSet.Any();
return !exists ? dbSet.Add(entity) : null;
}
}
When this does do an Add of my entity, I get back the new ID of the entity, but if it exists it returns null.
There will be times that I would like to get back the ID of an entity that already exists. However, the Key ID property of my entities will be different depending on the model. For instance, Address model's key is AddressId, Profile's key is ProfileId.
So, I'd like to modify this query (or make another version of it) to accept the Id property name as a parameter. (Or use EF to recognize the Primary key.) And do something like this:
public static class DbSetExtensions
{
public static T AddIfNotExists<T>(this DbSet<T> dbSet,
T entity,
Expression<Func<T, bool>> predicate = null,
Expression<Func<T, TId>> keyColumnName) where T : class, new()
{
var exists = predicate != null ? dbSet.Where(predicate).Select(e => e.keyColumnName) : dbSet.Any();
return !exists ? dbSet.Add(entity) : exists ;
}
}
I'm not sure if that is the correct way to define the property I want to use. I also realize this may not be as fast as just doing an Any(), but at times it may be necessary for us to get the ID.
I would also like to understand better what all of these items mean and how they work together. I've been all over and gotten bits and pieces, but haven't been able to put the whole puzzle together.
Well maybe there is a better solution, but you can try this:
public static TResult AddIfNotExists<T,TResult>(this DbSet<T> dbSet,
T entity,
Expression<Func<T, bool>> predicate,
Expression<Func<T, TResult>> columns
) where T : class, new()
{
if (predicate==null || columns==null)
throw new Exception();
//Find if already exist the entity and select its key column(s)
var result = dbSet.Where(predicate).Select(columns).FirstOrDefault();
//the result could be a reference type (string or anonymous type) or a value type
if (result!=null && !result.Equals(default(TResult)))
return result;
var newElement = dbSet.Add(entity);
//Compile the Expresion to get the Func
var func = columns.Compile();
//To select the new element key(s), add the element to an array (or List) to apply the Select method and get the keys
var r = new[]{newElement};
return r.Select(func).FirstOrDefault();
}
As you can see, I always expect the parameters predicate to check if the element already exist and columns to select the key or keys in case you have an entity with composite PK (but notice you can select all the properties you want from that entity, not just the keys). Then, I apply the expressions to filter and select the key(s) in case the element exist. If it doesn't exist, I add the entity to its DbSet<T>, but there is a problem. How can you get the new element key(s)? Well, the only solution I found to this was adding that element to an array, compiling the expression to get a Func<T,TResult>, and apply that Func<T,TResult> to the array, (I know this may not be the best solution but it should work).
To call this extension method, for example, for an entity that have a composite PK, you can do this:
.AddIfNotExists(element, e => e.UserName== "admin", e => new{ e.Id1,e.Id2});
The more simple in your case should be:
public static class DbSetExtensions
{
public static T AddIfNotExists<T>(this DbSet<T> dbSet, T entity,
Expression<Func<T, bool>> predicate = null) where T : class, new()
{
T exists = predicate != null ?
dbSet.Where(predicate).FirstOrDefault():
dbSet.FirstOrDefault();
return exists == null ?
dbSet.Add(entity):
exists;
}
}
then, as you seem to know the PK property, you can check it from the returned value.
I would like to pass an IQueryable and an array of ids to a method which filters the IQueryable based on those ids.
As the ids can be either long's or int's it should be solved generically.
I came up with the following:
public static IEnumerable<T> GetModified<TId, T>(IQueryable<T> objects, TId[] ids) where T : class
{
return objects.Where(j => ids.Contains((TId)j.GetType().GetProperty("Id").GetValue(j)));
}
Unfortunately I'm getting the exception:
LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object)' method, and this method cannot be translated into a store expression.
The exception is normal, as getting properties through reflection is something that clearly cannot be translated to SQL.
One thing I would try is to create a generic interface that exposes an Id property of a given type:
public interface HasId<T> {
T Id { get; set; }
}
Now you could declare your entity as implementing HasId<int>, for example, if the Id was of type int.
The next step is to modify your method like so:
public static IEnumerable<T> GetModified<TId, T>
(IQueryable<T> objects, TId[] ids) where T : class, HasId<TId>
{
return objects.Where(j => ids.Contains(j.Id));
}
Note the added generic restriction: where T : class, HasId<TId>. This enables you to write the simplified j.Id, which returns a TId value, instead of resorting to reflection.
Please note that I haven't run or tested this code; it's just an idea that I got when I saw your problem and I hope it helps.
Update:
Here's another possible solution that doesn't require that you declare interfaces or change your classes in any way:
public static IEnumerable<T> GetModified<TId, T>
(IQueryable<T> objects, TId[] ids, Expression<Func<T, TId>> idSelector)
where T : class
{
return objects.Where(j => ids.Contains(idSelector(j)));
}
What I've done here is add the Expression<Func<T, TId>> idSelector parameter, an expression that can return the Id of a given instance of T.
You would call the method like that:
var modified = GetModified(dbObjects, yourIdArray, entity => entity.Id);
(only the third parameter being new; keep the others as you have them now).
Again, I haven't tested if this works or even compiles, as I don't have a computer with VS here :(.
Entity Framework doesn't support some of the .NET methods such as GetValue() since it does not translate to SQL (which is the code actually executed to the IQueryable. Try calling ToList to get the CLR object before doing reflection:
public static IEnumerable<T> GetModified<TId, T>(IQueryable<T> objects, TId[] ids) where T : class
{
return objects.ToList().Where(j => ids.Contains((TId)j.GetType().GetProperty("Id").GetValue(j)));
}
I have a RepositoryBase class where I define basic crud methods for my Entity Framework Context. I have these two overloads of the All() method:
public virtual IQueryable<T> All<TKey>(Expression<Func<T, bool>> predicate)
{
return All().Where(predicate);
}
public virtual PagedResult<T> All<TKey>(int startRowIndex, int maximumRows,
Expression<Func<T, TKey>> orderingKey, Expression<Func<T, bool>> predicate,
bool sortDescending = false)
{
var subset = All().Where(predicate);
IEnumerable<T> result = sortDescending
? subset.OrderByDescending(orderingKey).Skip(startRowIndex).Take(maximumRows)
: subset.OrderBy(orderingKey).Skip(startRowIndex).Take(maximumRows);
//More code ommited
}
The first method always needs me to explicitly specify the entity type, but the second doesn't. Why is this?
Example, this doesn't compile:
return All(s => s.LoanApplicationId == loanApplicationId)
And instead I must call it like this:
return All<LoanApplication>(s => s.LoanApplicationId == loanApplicationId)
But this DOES compile:
return All(0,10, s => s.Name, s => s.LoanApplicationId == loanApplicationId, false)
TKey is in the parameter list of the second (via Expression<Func<T, TKey>> orderingKey) and not the first. That supplies enough for the second to successfully infer the type when you use it with your supplied arguments (s => s.Name). You don't give yourself that luxury in the first version, so the compiler forces you to fill in the details by supplying the type parameter explicitly.
And from the looks of it, you don't need TKey in the first anyway, so possibly get rid of it (unless there is more code visible than that relatively simple implementation). And I don't think it means what your sample invocation thinks it means. TKey in the second is likely string (whatever the type of s.Name is), for example.
I'm trying to create a generic repository for my models. Currently i've 3 different models which have no relationship between them. (Contacts, Notes, Reminders).
class Repository<T> where T:class
{
public IQueryable<T> SearchExact(string keyword)
{
//Is there a way i can make the below line generic
//return db.ContactModels.Where(i => i.Name == keyword)
//I also tried db.GetTable<T>().Where(i => i.Name == keyword)
//But the variable i doesn't have the Name property since it would know it only in the runtime
//db also has a method ITable GetTable(Type modelType) but don't think if that would help me
}
}
In MainViewModel, I call the Search method like this:
Repository<ContactModel> _contactRepository = new Repository<ContactModel>();
public void Search(string keyword)
{
var filteredList = _contactRepository.SearchExact(keyword).ToList();
}
Solution:
Finally went with Ray's Dynamic Expression solution:
public IQueryable<TModel> SearchExact(string searchKeyword, string columnName)
{
ParameterExpression param = Expression.Parameter(typeof(TModel), "i");
Expression left = Expression.Property(param, typeof(TModel).GetProperty(columnName));
Expression right = Expression.Constant(searchKeyword);
Expression expr = Expression.Equal(left, right);
}
query = db.GetTable<TModel>().Where(Expression.Lambda<Func<TModel, bool>>(expr, param));
Interface solution
If you can add an interface to your object you can use that. For example you could define:
public interface IName
{
string Name { get; }
}
Then your repository could be declared as:
class Repository<T> where T:class, IName
{
public IQueryable<T> SearchExact(string keyword)
{
return db.GetTable<T>().Where(i => i.Name == keyword);
}
}
Alternate interface solution
Alternatively you could put the "where" on your SearchExact method by using a second generic parameter:
class Repository<T> where T:class
{
public IQueryable<T> SearchExact<U>(string keyword) where U: T,IName
{
return db.GetTable<U>().Where(i => i.Name == keyword);
}
}
This allows the Repository class to be used with objects that don't implement IName, whereas the SearchExact method can only be used with objects that implement IName.
Reflection solution
If you can't add an IName-like interface to your objects, you can use reflection instead:
class Repository<T> where T:class
{
static PropertyInfo _nameProperty = typeof(T).GetProperty("Name");
public IQueryable<T> SearchExact(string keyword)
{
return db.GetTable<T>().Where(i => (string)_nameProperty.GetValue(i) == keyword);
}
}
This is slower than using an interface, but sometimes it is the only way.
More notes on interface solution and why you might use it
In your comment you mention that you can't use an interface but don't explain why. You say "Nothing in common is present in the three models. So i think making an interface out of them is not possible." From your question I understood that all three models have a "Name" property. In that case, it is possible to implement an interface on all three. Just implement the interface as shown and ", IName" to each of your three class definitions. This will give you the best performance for both local queries and SQL generation.
Even if the properties in question are not all called "Name", you can still use the nterface solution by adding a "Name" property to each and having its getter and setter access the other property.
Expression solution
If the IName solution won't work and you need the SQL conversion to work, you can do this by building your LINQ query using Expressions. This more work and is significantly less efficient for local use but will convert to SQL well. The code would be something like this:
class Repository<T> where T:Class
{
public IQueryable<T> SearchExact(string keyword,
Expression<Func<T,string>> getNameExpression)
{
var param = Expression.Parameter(typeof(T), "i");
return db.GetTable<T>().Where(
Expression.Lambda<Func<T,bool>>(
Expression.Equal(
Expression.Invoke(
Expression.Constant(getNameExpression),
param),
Expression.Constant(keyword),
param));
}
}
and it would be called thusly:
repository.SearchExact("Text To Find", i => i.Name)
Ray's method is quite good, and if you have the ability to add an interface definitely the superior however if for some reason you are unable to add an interface to these classes (Part of a class library you can't edit or something) then you could also consider passing a Func in which could tell it how to get the name.
EG:
class Repository<T>
{
public IQueryable<T> SearchExact(string keyword, Func<T, string> getSearchField)
{
return db.GetTable<T>().Where(i => getSearchField(i) == keyword);
}
}
You'd then have to call it as:
var filteredList = _contactRepository.SearchExact(keyword, cr => cr.Name).ToList();
Other than these two options you could always look into using reflection to access the Name property without any interface, but this has the downside that there's no compile-time check that makes sure the classes you're passing actually DO have a Name property and also has the side-effect that the LINQ will not be translated to SQL and the filtering will happen in .NET (Meaning the SQL server could get hit more than is needed).
You could also use a Dynamic LINQ query to achieve this SQL-side effect, but it has the same non type-safe issues listed above.