I have multiple tables with the same structure and want to write a generic function that return data from the correct table.
Now:
var a = context.TableA.Where(a => a.id == id).FirstOrDefault.Name;
var b = context.TableB.Where(b => b.id == id).FirstOrDefault.Name;
I want something like this:
var a = GetName<TableA>(id);
var b = GetName<TableB>(id);
GetName<T>(int id){
//right table
return context<T>.Where(x => x.id == id).FirstOrDefault.Name;
}
But I can't get the context to get the right table with generic T.
You can not do this without getting context, so, you can add another class, pass context to it as argument of constructor, save it to private field and define GetName method inside. Like this:
public class EntityHelper
{
private readonly DbContext _context;
public EntityHelper(DbContext context)
{
this._context = context;
}
public string GetName<T>(int id)
{
return this._context.Set<T>().Where(x => x.id == id).FirstOrDefault().Name;
}
}
for method GetName there should be constraint, because compiler don't know does T has property called Name
also I recommend you to change FirstOrDefault() to First(), because if you will access property of FirstOrDefault() result, when it's return null - you'll get NullReferenceException
Try something like:
context.Set(typeof(T)).Where(a => a.id == id).FirstOrDefault.Name;
Where T should have restriction on having ID.
You can use the following helper (no changes to your classes is needed)
public static class Utils
{
public static string GetName<T>(this DbContext db, int id)
where T : class
{
var source = Expression.Parameter(typeof(T), "source");
var idFilter = Expression.Lambda<Func<T, bool>>(
Expression.Equal(Expression.Property(source, "id"), Expression.Constant(id)),
source);
var nameSelector = Expression.Lambda<Func<T, string>>(
Expression.Property(source, "Name"),
source);
return db.Set<T>().Where(idFilter).Select(nameSelector).FirstOrDefault();
}
}
like this
var a = context.GetName<TableA>(id);
var b = context.GetName<TableB>(id);
Related
I want to reflect model classes of a MVC project by its name which I have in a string variable refName. Currently I am using switch case to use those class as template i.e <T>. How can we do this part better, so that if a new class comes in then I don't want to insert a new case in this switch statement.
Basically, what I want to do is to collect data from cosmos db into a specific template class format according to a field gRefType. Here's what I have done:
IEnumerable<Object> itemsRefDetail;
switch (refName)
{
case "AMCaseStatus":
itemsRefDetail = await DocumentDBRepository<AMCaseStatus>.GetItemRefDetailAsync(p => p.GrefType == refName && p.Tag == tag, collectionId);
break;
case "AMcaseSubStatus":
itemsRefDetail = await DocumentDBRepository<AMcaseSubStatus>.GetItemRefDetailAsync(p => p.GrefType == refName && p.Tag == tag, collectionId);
break;
case "AMRole":
itemsRefDetail = await DocumentDBRepository<AMRole>.GetItemRefDetailAsync(p => p.GrefType == refName && p.Tag == tag, collectionId);
break;
}
As you can see in the above code, Template class used in each case is as same as case value. All the classes have both the properties(GrefType and Tag) in common.
Here is the DocumentDbRepository class:
public static class DocumentDBRepository<T> where T : class
{
public static async Task<IEnumerable<T>> GetItemRefDetailAsync(Expression<Func<T, bool>> predicate, string collectionId)
{
IDocumentQuery<T> query = client.CreateDocumentQuery<T>(
UriFactory.CreateDocumentCollectionUri(DatabaseId, collectionId))
.Where(predicate)
.AsDocumentQuery();
FeedResponse<T> privilegeQueryResponse = await query.ExecuteNextAsync<T>();
return privilegeQueryResponse;
}
}
I made an example of how to reflect generic types, The only thing you should do is, wrap up the GrefType and Tag into a baseclass/interface. This might give you a startup:
The BaseType is the basetype of your data, which contains the fields you can use in your lambda function.
Data is your data service (client.CreateDocumentQuery), in this case some test data.
DocumentDBRepository contains the static SelectData method, which executes the lambda function passed as parameter.
MyLookupThing is derived from the BaseType, used in reflection.
TestReflection does the reflection and executes it.
Here's the code:
// The basetype of an item (which contains de RefName etc)
public class BaseType
{
public string RefName { get; set; }
public string Tag { get; set; }
}
// static T service with some testdata (mock)
public static class Data<T> where T : BaseType
{
public static List<T> MyData { get; } = new List<T>();
static Data()
{
// create an item
var item = Activator.CreateInstance<T>();
item.RefName = "Test";
item.Tag = "Bla";
MyData.Add(item);
var item2 = Activator.CreateInstance<T>();
item2.RefName = "SomethingElse";
item2.Tag = "TagThing";
MyData.Add(item2);
var item3 = Activator.CreateInstance<T>();
item3.RefName = "Test2";
item3.Tag = "Bla2";
MyData.Add(item3);
}
}
// the generic class which uses the basetype as generic
public static class DocumentDBRepository<T> where T : BaseType
{
public static IEnumerable<T> SelectData(Func<T, bool> predicate)
{
// some static test data
return Data<T>.MyData.Where(predicate);
}
}
// your derived class from BaseType
public class MyLookupThing : BaseType
{
}
class TestReflection
{
public TestReflection()
{
// you can create more classes derived from BaseType
var typeStr = "TestRef.MyLookupThing";
// resolve the type:
var lookupType = (from ass in AppDomain.CurrentDomain.GetAssemblies()
from t in ass.GetTypes()
where t.FullName == typeStr
select t).First();
// get the type of the generic class
var myType = typeof(DocumentDBRepository<>);
// create a generic type
var myGenericType = myType.MakeGenericType(lookupType);
var method = myGenericType.GetMethod("SelectData", BindingFlags.Static | BindingFlags.Public);
// Create the function (with the BaseType)
var func = new Func<BaseType, bool>(item => item.RefName.StartsWith("Test"));
// invoke the method of the generic class
IEnumerable<BaseType> result = (IEnumerable<BaseType>)method.Invoke(null, new object[] { func });
// show the results
foreach (var item in result)
Console.WriteLine(item.RefName);
Console.ReadKey();
}
}
Which will give:
Test
Test2
as result
Here is how you can create this expression that limits the query to the type:
internal static Expression<Func<TEntity, bool>> CreateTypeSpesificExpression<TEntity>(string refName) where TEntity : class
{
var parameter = Expression.Parameter(typeof(IAmObject));
var member = Expression.Property(parameter, nameof(IAmObject.GrefType));
var contant = Expression.Constant(refName);
var body = Expression.Equal(member, contant);
var extra = Expression.Lambda<Func<TEntity, bool>>(body, parameter);
return extra;
}
This is all assuming that your AM classes have the same interface.
Once you do that you can simple add this in your where clause.
Your query should look like this:
IEnumerable<Object> itemsRefDetail = await DocumentDBRepository<AMCaseStatus>.GetItemRefDetailAsync(p => CreateTypeSpesificExpression(refName) && p.Tag == tag, collectionId);
Also something worth pointing out is that Cosmonaut already has native support for type based collection sharing.
My repository returns entities derived from a common base class
class BaseClass
{
public int Id { get; set; }
}
class MyClass : BaseClass
{
public string Name { get; set; }
}
class MyOtherClass : BaseClass
{
...
}
in a function like this:
IQueryable<T> GetEntities<T>() where T : BaseClass
I added a method to register additional filters for specific entities as lambdas using Expression<Func<T,bool>> like this:
RegisterFilter<MyClass>(t => t.Name == "Test" );
that will be applied whenever GetEntities is called with MyClass in the type argument.
Question
How can I create an expression dynamically at runtime that wraps a type cast around the filter?
in my specific case GetEntities is called on an IQueryable<MyClass> using the BaseClass as type argument and event tough I know that the filter for MyClass needs to applied I did not find a way to do so:
IQueryable<BaseClass> src =
(new List<MyClass>
{
new MyClass { Id = 1, Name = "asdf" },
new MyClass { Id = 2, Name = "Test" }
})
.AsQueryable();
Expression<Func<MyClass, bool>> filter = o => o.Name == "Test";
// does not work (of course)
src.Where(filter);
Failed attempts
Obviously I could cast the collection back before calling Where but my attempts to do this at runtime did not work:
// HACK: don't look
var genericCast = typeof(Queryable).GetMethod("Cast").MakeGenericMethod(entityType);
var genericWhere = typeof(Queryable).GetMethods().Single(mi => mi.Name == "Where" && mi.GetParameters()[1].ParameterType.GenericTypeArguments[0].Name == "Func`2");
q = (IQueryable<T>)genericCast.Invoke(q, new object[] { genericWhere.Invoke(q, new object[] { filterExp }) });
System.InvalidOperationException: 'Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.'
since this is also very ugly I tried to wrap my filter in cast like this:
LambdaExpression filterExp = (LambdaExpression)filter;
var call = filterExp.Compile();
Expression<Func<T, bool>> wrap = o => (bool)call.DynamicInvoke(Convert.ChangeType(o, entityType));
This works but prevents the filter to be generated into a store expression so it will be done in memory which is also undesirable.
I feel like the solution to this should be trivial but I can't seem to get it right so any help would be appreciated very much.
You can:
// Using SimpleExpressionReplacer from https://stackoverflow.com/a/29467969/613130
public static Expression<Func<BaseClass, bool>> Filter<TDerived>(Expression<Func<TDerived, bool>> test)
where TDerived : BaseClass
{
// x =>
var par = Expression.Parameter(typeof(BaseClass), "x");
// x is TDerived
var istest = Expression.TypeIs(par, typeof(TDerived));
// (TDerived)x
var convert = Expression.Convert(par, typeof(TDerived));
// If test is p => p.something == xxx, replace all the p with ((TDerived)x)
var test2 = new SimpleExpressionReplacer(test.Parameters[0], convert).Visit(test.Body);
// x is TDerived && test (modified to ((TDerived)x) instead of p)
var and = Expression.AndAlso(istest, test2);
// x => x is TDerived && test (modified to ((TDerived)x) instead of p)
var test3 = Expression.Lambda<Func<BaseClass, bool>>(and, par);
return test3;
}
Note that I'm using the SimpleExpressionReplacer I wrote in another answer.
Given a test like:
var exp = Filter<MyClass>(p => p.Name == "Test");
the resulting expression is:
x => x is MyClass && ((MyClass)x).Name == "Test"
I need to get only one value from the table, I do it like this:
public DBEntities db = new DBEntities();
public ObservableCollection<Users> GetUsers
{
get { return db.Users.Local; }
}
public WindowViewModel(Label userName)
{
var us = GetUsers.Where(u => u.Id == 1) as Users;
userName = us.Name.ToString();
}
But it does not work out the error: Additional information: The object reference does not point to an instance of the object.
Let me give you some direction
Do not use a propertie to get your data, use a method
like this:
//returns an enumeration of users filtered by the given expression
Public IEnumerable<User> GetUsers(Expression<Func<User, bool>> expression)
{
return db.Users.Where(expression);
}
// returns the first occurency of a user filtered by the given expression
Public User GetUser(Expression<Func<User, bool>> expression)
{
return db.Users.FirstOrDefault(expression);
}
usage:
var user = GetUser(u => u.Id == 1); // returns first occurency or null
var users = GetUsers(u => u.Id < 100);
and you have a label, it is an object, with properties where you should set the information, so set your Name to its Content:
public WindowViewModel(Label userName)
{
var us = GetUser(u => u.Id == 1);
userName.Content = us.Name.ToString();
}
I have a very simple code of what I am trying to achieve here.
Basically the idea here would be to be able to do a simple FindBy(x => x.<the_column_name>, <the value>); therefore I wouldn't have to copy-paste the same query by changing only the column name.
For now I keep getting an exception from LINQ saying LINQ to Entities does not recognize the method 'System.String Invoke(Spunky.Entities.User)' method, and this method cannot be translated into a store expression
Is that something possible ? Or maybe we're not yet there with EF 6.1?
public class UsersRepository
{
private Lazy<IDataContext> context;
public UsersRepository()
: this(() => new DataContext())
{
}
public UsersRepository(Func<IDataContext> context)
{
this.context = new Lazy<IDataContext>(context);
}
public IQueryable<User> Find(int id)
{
return FindBy(u => u.Id, id);
}
public IQueryable<User> Find(string username)
{
return FindBy(u => u.Username, username);
}
public IQueryable<User> FindBy<T>(Func<User, T> property, T value)
{
return context.Value
.Users
.Where(u => property.Invoke(u).Equals(value));
}
}
If you change the signature
IQueryable<User> FindBy(Expression<Func<User, bool>> predicate)
{
return context.Users.Where(predicate);
}
you can call
return FindBy(u => u.Username == username);
The code hardly changes, but you don't need to manufacture expressions.
You have to enter an expression (not a Func) because expressions can be translated into SQL. A Func is just a .Net delegate, for which no SQL equivalent exists.
You need to construct an expression tree linq to entities can translate to sql:
public IQueryable<T> FindBy<TKey>(Expression<Func<T,TKey>> memberExpression, object value)
{
var parameterVisitor = new ParameterVisitor(memberExpression);
var parameter = parameterVisitor.Parameter;
var constant = Expression.Constant(value);
var equal = Expression.Equal(memberExpression,constant);
var predicate = Expression.Lambda(equal, parameter);
return context.Value.Users.Where(predicate);
}
public class ParameterVisitor : ExpressionVisitor
{
public ParameterExpression Parameter { get;set;}
public ParameterVisitor(Expression expr)
{
this.Visit(expr);
}
protected override VisitParameter(ParameterExpression parameter)
{
Parameter = parameter;
return parameter;
}
}
Still untested from the top of my head.
Edit: Code corrected.
Is there a way I can make the following db query builder generic?
private IQueryable<Foo> ByName(IQueryable<Foo> dbQuery, Query query)
{
string[] searchTerms = query.Data.Replace(" ","").ToLower().Split(',');
if (query.Exclude)
{
return dbQuery.Where(x => searchTerms.All(
y => y != x.Name.Replace(" ", "").ToLower()));
}
return dbQuery.Where(x => searchTerms.Any(
y => y == x.Name.Replace(" ", "").ToLower()));
}
I've got the same function for lots of different properties of Foo. ByCounty, ByTown, ByStreet etc etc.
I've written some functions that return linq before like the following
public Expression<Func<Foo, bool>> FoosAreWithinDistanceFromGeocode(
double distance, Geocode geocode)
{
double distanceSquare = distance * distance;
return foo => ( SqlFunctions.Square((double)(
foo.Address.Geocode.Easting - geocode.Easting)) +
SqlFunctions.Square((double)(fooAddress.Geocode.Northing -
geocode.Northing)) ) <= distanceSquare;
}
But I can't seem to find if the Linq-to-SQL stuff can't use generics or if it's possible to pass properties as generics and that kind of thing.
EDIT: I have this working generically for a single search term.
Where [query.Data == "Foo1"]
return dbQuery.Where(SearchMatch("Name", query.Data));
public Expression<Func<Foo, bool>> SearchMatch(string propertyName, string searchTerm)
{
var foo = Expression.Parameter(typeof(Foo), "foo");
var prop = Expression.Property(foo, propertyName);
var search = Expression.Constant(searchTerm);
var equal = Expression.Equal(prop, search);
return Expression.Lambda<Func<Foo, bool>>(equal, foo);
}
Anyone have any ideas how to make it work for an array of strings?
You need to define an interface that exposes the properties that you want to access, like so:
public interface IHaveName
{
string Name { get; }
}
Then, on your classes, you would implement the interface:
public class Foo : IHaveName
If you're using the classes generated from a DBML file, these classes are marked with the partial keyword so implementing the interface is as simple as creating a new file, and inserting:
public partial class Foo : IHaveName
Since the property is already declared as public in the other .cs file generated from the .dbml file, the interface is implemented implicitly.
Finally, you would rewrite your ByName method to take a generic type parameter with a constraint that it implement your interface IHaveName:
private IQueryable<T> ByName<T>(IQueryable<T> dbQuery, Query query)
where T : IHaveName
{
// Everything else is the same.
For your other properties (and methods which use them), you could aggregate them together into one interface, or separate them out, depending on your needs.
Based on your edit, if you want to create an expression dynamically, you don't have to give up compile-time safety:
public Expression<Func<Foo, bool>> SearchMatch(
Expression<Func<Foo, string>> property, string searchTerm)
{
var foo = Expression.Parameter(typeof(Foo), "foo");
// Get the property info from the property expression.
var prop = Expression.Property(foo,
(property.Body as MemberExpression).Member as PropertyInfo);
var search = Expression.Constant(searchTerm);
var equal = Expression.Equal(prop, search);
return Expression.Lambda<Func<Foo, bool>>(equal, foo);
}
Which you then call like so:
var expression = SearchMatch(f => f.Name, "searchTerm");
This ensures that the properties that you are passing to SearchMatch actually exist on Foo. Note if you wanted to make this generic for other scalar property types, you would do the following:
public Expression<Func<Foo, bool>> SearchMatch<T>(
Expression<Func<Foo, T>> property, T searchTerm)
{
var foo = Expression.Parameter(typeof(Foo), "foo");
// Get the property info from the property expression.
var prop = Expression.Property(foo,
(property.Body as MemberExpression).Member as PropertyInfo);
var search = Expression.Constant(searchTerm);
var equal = Expression.Equal(prop, search);
return Expression.Lambda<Func<Foo, bool>>(equal, foo);
}
If I understood what you are trying to achieve reflection might help you. At least if you play nice. Here's a simplified but working example
internal class Program
{
private class Data
{
public string Name { get; set; }
public string Address { get; set; }
public override string ToString()
{
return String.Format("My name is {0} and I'm living at {1}", Name, Address);
}
}
static Expression<Func<Data,bool>> BuildExpression(PropertyInfo prop, IQueryable<string> restrict)
{
return (data) => !restrict.Any(elem => elem == prop.GetValue(data, null));
}
static IQueryable<Data> FilterData(IQueryable<Data> input, Expression<Func<Data, bool>> filter)
{
return input.Where(filter);
}
public static void Main (string[] args)
{
List<Data> list = new List<Data>()
{
new Data {Name = "John", Address = "1st Street"},
new Data {Name = "Mary",Address = "2nd Street"},
new Data {Name = "Carl", Address = "3rd Street"}
};
var filterByNameExpression = BuildExpression(typeof (Data).GetProperty("Name"),
(new List<string> {"John", "Carl"}).AsQueryable());
var filterByAddressExpression = BuildExpression(typeof(Data).GetProperty("Address"),
(new List<string> { "2nd Street"}).AsQueryable());
IQueryable<Data> filetedByName = FilterData(list.AsQueryable(), filterByNameExpression);
IQueryable<Data> filetedByAddress = FilterData(list.AsQueryable(), filterByAddressExpression);
Console.WriteLine("Filtered by name");
foreach (var d in filetedByName)
{
Console.WriteLine(d);
}
Console.WriteLine("Filtered by address");
foreach (var d in filetedByAddress)
{
Console.WriteLine(d);
}
Console.ReadLine();
}
Hovewer, I'\m almost sure it won't work with LINQ-to-SQL. One way to workaround it is to materialize the IQueryable before passing it to such filtering methods (e.g. by calling ToList on them).