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.
Related
I have been reading posts about Expression<Func<TModel,TResult>> for about an hour and I really do not understand this. My apologizes, but I just don't.
I have an issue that I have a abstract class that has a call to EF6 where I need to order by some property that I would like to define in the child class. That said I will add an example below.
public abstract MyController<TModel>:ApiController
{
protected IRepository<TModel> Repository {get;}
protected MyController(IRepository<TModel> repo)
{
Repository = repo;
}
protected Expression<Func<TModel,TResult>> OrderBy {get; set}
public IHttpActionResult GetItems()
{
return Ok(Repository.Get().OrderBy(x=>OrderBy(x)).ToList()); //with lots of other cool stuff.
}
}
public PersonController:MyController<Person>
{
public PersonControler(IRepository<Person> repo):base(repo)
{
OrderBy = //I need help here
}
}
Okay, so after all of this I have been reading about Expressions and I do not understand why OrderBy = (person)=> person.LastName will not work here. Can someone please explain how the Expression works and how to make this work?
Declare your property as
protected Expression<Func<TModel, object>> OrderBy { get; set; }
Assign it in PersonController
OrderBy = p => p.LastName
And use it like
Repository.Get().OrderBy(OrderBy).ToList()
Under the hood (of EF/LINQ query translation engine) Expression (actually all methods call chain) is translated to SQL query. There is expression visitor (see pattern 'visitor'), which does the job.
To get things work:
method Get of IRepository should return type DbSet<TModel> (for EF) or Table<TModel> (for LINQ to SQL)
type of ordering key should be defined either in abstract class (public abstract MyController<TModel, TKey> : ApiController) or in expression directly (protected Expression<Func<TModel, int>> OrderBy {get; set})
I am retrieving some tuples from a database that are mapped to entity classes by means of Entity Framework.
For these entities, I have a key selector function (supplied at runtime by other developers) that I would like to pass to Queryable.OrderBy. The key selector function is provided upon "registration" of the entity type in my system - which happens by means of a method that looks roughly like this:
public void RegisterEntity<TEntity, TKey>(string entityName, TKey defaultKey, Func<TEntity, TKey> keySelectorFunc)
I would like to execute this OrderBy call before materializing the results to entity objects (i.e. in such a way that the OrderBy call still gets translated to SQL under the hood).
The problem is that the entities have composite keys, and thus, the key selector function will return a custom object instantiated in the function. You can imagine it like this:
var keySelectorFunc = e => new CustomKey(e.Value1, e.Value2);
As usual, Entity Framework does not like this (the usual "Only parameterless constructors and initializers are supported in LINQ to Entities" error).
Is there any way to use such a custom key selector function to return a custom key? Do I have to resort to anonymous classes? Or should I move the OrderBy call to a place after I have left the LINQ-to-Entities world?
In this particular case it would be easy to use Sort method of Generic List.
https://msdn.microsoft.com/en-us/library/3da4abas(v=vs.110).aspx
Sort method requires the type of the list to implement IComparable interface and it uses the implementation of CompareTo method from IComparable interface. Otherwise implementation of IComparer also can be passed to this method.
So if your entity class is already implemeting IComparable interface then this should surely work for you. You will have to to .ToList() on the IQueryable result of course before you can call the Sort method on it.
public class Category : IComparable<Category>
{
public int CategoryId { get; internal set; }
public string CategoryName { get; internal set; }
public int CompareTo(Category x)
{
return String.Compare(x.CategoryName, this.CategoryName, StringComparison.InvariantCulture);
}
}
List<Category> categories = new List<Category>();
categories.Add(new Category {CategoryName = "Cate1"});
categories.Add(new Category {CategoryName = "Cate2"});
categories.Sort();
foreach (var cat in categories)
{
Console.WriteLine(cat.CategoryName);
}
This displays me category names in reverse order based on the comparison logic I have written in the CompareTo method of Category Class.
In this case I think the best way is use a custom ExtensionMethod to avoid any overhead of coding or unnecessary complexity to do that.
See if it implementation can help you.
First we create your customkey class that is responsable to create the statement expressions:
class CustomKey
{
public CustomKey(params string[] value)
{
if(!value.Any())
throw new InvalidOperationException("Select at least one Property for this operation");
Values = new List<string>();
Values.AddRange(value);
}
private List<string> Values { get; set; }
// this method run throughout all property configured to create the expressions
public void ForEachProperty<TSource, TKey>(Action<Expression<Func<TSource, TKey>>, bool> method)
{
bool firstItem = true;
Values.ForEach(f =>
{
var expression = CreateExpression<TSource, TKey>(f);
method(expression, firstItem);
firstItem = false;
});
}
// this method is responsable to create each expression
Expression<Func<TSource, TKey>> CreateExpression<TSource, TKey>(string property)
{
var parameter = Expression.Parameter(typeof(TSource), "x");
var member = typeof(TSource).GetMember(property).FirstOrDefault();
Expression body = Expression.MakeMemberAccess(parameter, member);
return Expression.Lambda<Func<TSource, TKey>>(Expression.Convert(body, typeof(object)), parameter);
}
}
After that we create your custom ExtesionMethod, somethink like that:
public static class OrderByExtensionClass
{
// instead of try passing an expression, we pass our CustomKey object with the columns to sort.
// than this method create the apropriate OrderBy Expression statement
public static IOrderedQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, CustomKey customKey)
{
// the parameter isFirst is just to control where we are to build the expression
customKey.ForEachProperty<TSource, object>((expression, isFirst) =>
{
if (isFirst)
source = source.OrderBy(expression);
else
source = ((IOrderedQueryable<TSource>)source).ThenBy(expression);
});
return ((IOrderedQueryable<TSource>)source);
}
}
After that we just do:
CustomKey custom = new CustomKey("Name", "Age");
myEntityContext.People.OrderBy(custom).ToList()
I hope it can help you.
Part of the problem, I think, is that OrderBy wouldn't know what to do with a complex type. SQL Server knows how to order by primitive types, but that's about it. You would have to do something like ...OrderBy(x=>x.Field1).ThenBy(x=>x.Field2). You could write an extension method that takes the key, extracts the property names from the key, and builds the .OrderBy().ThenBy() expression, as long as you know what the key will be before executing the query. Otherwise yeah, you may have to materialize the results before ordering.
public T FindById(object id)
{
return session.Get<T>(id);
}
I want to get object by Name
Something like this, but I am not able to write a query with type T
session.Get<T>().Where(x => x.something == something).SingleOrDefault();
or is there any alternative to this approach?
You can use Expression trees and build something like:
protected T Get<T, TValue>(string propertyName, TValue value) where T : class
{
var par = System.Linq.Expressions.Expression.Parameter(typeof(T), "x");
var eq = System.Linq.Expressions.Expression.Equal(System.Linq.Expressions.Expression.Property(par, propertyName), System.Linq.Expressions.Expression.Constant(value));
var lambda = System.Linq.Expressions.Expression.Lambda<Func<T, bool>>(eq, par);
return session.QueryOver<T>().Where(lambda).SingleOrDefault();
}
Note that I'm using the full namespace + classname because NHibernate has another Expression class.
There is a native solution in NHiberante:
Chapter 15. Criteria Queries
This Criteria API (which is there from the beginning) is really what we need here. For example the QueryOver API - is built on top of that.
The biggest benefit is, it does work natively with "strings". This would be very simple, easy to maintain piece of code:
public T GetByProperty<T>(string propertyName, object value)
where T: class
{
var session = ... // get a session
return session.CreateCriteria<T>()
.Add(Restrictions.Eq(propertyName, value))
.SetMaxResults(1)
.List<T>()
.FirstOrDefault();
}
There is a complete doc
You have to restrict T to something with a particular property. You could, for instance, create a superclass called NamedObject which has a property Name and make all nameable objects inherit from it. Restricting T to a NameableObject or descendant would then allow you to access the Name property in your query.
Not particularly elegant but it should work.
I think you should use reflection for query a generic type, just like this:
session.Get<T>().Where(P => typeof(P).GetPropery("PropertyName").GetValue(P).ToString() == "Something").SingleOrDefault();
In this code, typeof(oObject) returns de a Type intance of the object, then the GetProperty("PropertyName") method returns you a System.Reflection.PropertyInfo with that you can get the property value with GetValue(P) method that receive an instance of the object that contain the property in this case is [P] create in Lamba expression.
Im not sure what you mean by "I am not able to write a query with type T".
But you can call with expression where refactor is supported.
public T GetByExpression<T>(Expression<Func<T, bool>> restriction) where T : class
{
return Session.QueryOver<T>().Where(restriction).SingleOrDefault();
}
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've got a few tables that all have the same column domainID which basically just controls what data gets displayed on which website, as they share a database.
So when I go to databind a table to a control I would need to create a large switch to handle the different LINQ queries. I would like to create a utility method which takes the table type as a parameter and then return a where clause based on a column in passed table.
public static IEnumerable<T> ExecuteInContext<T>(
IQueryable<T> src)
{
int domain = 1;//hard coded for example
return src.Where(x => x.DomainID == domain);//Won't work, has to be a way to do this.
}
I'm stuck on the return code. You can't simply construct a where clause like I currently am because it doesn't know what table i'm talking about.
I'm trying to call that first method like this:
using (DataClasses1DataContext db = new DataClasses1DataContext())
{
var q = Utility.ExecuteInContext(db.GetTable<item>());
Repeater1.DataSource = q;
Repeater1.DataBind();
}
I hope this explains what I'm trying to do.
Edit: BrokenGlass's answer solved my problem. I would like to add that you need to open up your .dbml.cs file and extend the table/class with your interface. I also wanted to point out that the project wouldn't build if my column was nullable, it said it wasn't the same return type as my interface.
You have to restrict your T to a class that has a property of DomainID - you can add these interface implementations in partial classes that extend your data model.
public interface IFoo
{
int DomainId { get; set; }
}
..
public static IQueryable<T> ExecuteInContext<T>(IQueryable<T> src) where T: IFoo
{
int domain = 1;//hard coded for example
return src.Where(x => x.DomainID == domain);
}
Expression pe = Expression.Parameter(typeof(T));
Expression prope = Expression.Property(pe, "DomainID");
Expression ce = Expression.Equals(prope,
Expression.Constant((int)1);
Expression<Func<T,bool>> exp =
Expression.Lambda<Func<T,bool>>(
ce, pe);
return query.Where(exp);
You should be able to cast your generic parameter to the intended type...
public static IEnumerable<T> ExecuteInContext<T>(IQueryable<T> src)
{
int domain = 1;//hard coded for example
return src.Where(x => ((T)x).DomainID == domain);
}
But you realize you've created a generic method that assumes its type parameter will always expose a specific property? If you're going to do that, you should apply a generic type constraint such that T is always derived from a type that has that property...
For example:
public static IEnumerable<T> ExecuteInContext<T>(IQueryable<T> src) where T : IMyDomainObject
I'm not sure if I understand what you mean, but maybe you want to add a where clause:
public static IEnumerable<T> ExecuteInContext<T>(IQueryable<T> src)
where T: MyType //MyType exposing your DomainId
{
int domain = 1;//hard coded for example
return src.Where(x => x.DomainID == domain);//Won't work, has to be a way to do this.
}