Using 'Contains' Clause Like 'IN' SQL Clause in Entity Framework - c#

I am working on a tag cloud application. There are 3 database tables.
Content: ContentID, Text
Tag: TagID, Name
TagRelation: TagRelationID, TagID, ContentID
The code below is wrong. Because 'Contains' clause doesn't take a list of parameters like the 'IN' SQL clause. Is there an alternative clause that I can use to make this code work?
public List<Content> GetTagArticles(String tagName)
{
var relations = db.TagRelation.Where(t => t.Tag.Name == tagName).ToList();
var contents = db.Content.Where(c => c.TagRelation.Contains(relations)).ToList();
return contents.ToList();
}

Try the following:
var contents = db.Content.SelectMany(c => c.TagRelation).Where(tr => relations.Contains(tr)).ToList();

Probably this Stackoverflow thread can help you, at least I think you are looking for this...
Excerpt from the thread:
you could implement your own WhereIn method :
public static IQueryable<TEntity> WhereIn<TEntity, TValue>
(
this ObjectQuery<TEntity> query,
Expression<Func<TEntity, TValue>> selector,
IEnumerable<TValue> collection
)
{
if (selector == null) throw new ArgumentNullException("selector");
if (collection == null) throw new ArgumentNullException("collection");
ParameterExpression p = selector.Parameters.Single();
if (!collection.Any()) return query;
IEnumerable<Expression> equals = collection.Select(value =>
(Expression)Expression.Equal(selector.Body,
Expression.Constant(value, typeof(TValue))));
Expression body = equals.Aggregate((accumulate, equal) =>
Expression.Or(accumulate, equal));
return query.Where(Expression.Lambda<Func<TEntity, bool>>(body, p));
}
Usage:
public static void Main(string[] args)
{
using (Context context = new Context())
{
//args contains arg.Arg
var arguments = context.Arguments.WhereIn(arg => arg.Arg, args);
}
}
Your example (untested): (and doing 2 queries :( )
public List<Content> GetTagArticles(String tagName)
{
var relationIds = db.TagRelation.Where(t => t.Tag.Name == tagName).Select(t=>t.Id).ToList();
var contents = db.Content.WhereIn(c => c.TagRelation.Id, relationIds>);
return contents.ToList();
}

Related

Building a dynamic linq Func to return a string

I'm trying to figure out for a IQueryable how I can build a csv file by dynamically selecting objects as strings.
for example:
I read this about dynamically selecting properties of a T ...
LINQ : Dynamic select
That would allow me to do something like this ...
var data = new List<T> { items };
var fields = new string[] { "Field1", "Field2", "Field3" };
// build row strings
var rows = set.Select(BuildRowObjectExpression<T, ProjectionOfT>(fields))
.Select(i => Serialise<ProjectionOfT>(i));
string Serialise<T>(T i, string separator)
{
var properties = typeof(T).GetProperties();
var values = new List<string>();
foreach (var p in properties)
values.Add(p.GetValue(i).ToString());
return string.Join(separator, values);
}
Func<T, Tout> BuildRowObjectExpression<T, Tout>(string[] fields)
{
// input parameter "o"
var xParameter = Expression.Parameter(typeof(T), "o");
// new statement "new Data()"
var xNew = Expression.New(typeof(T));
// create initializers
var bindings = fields.Select(o => {
// property "Field1"
var mi = typeof(T).GetProperty(o);
// original value "o.Field1"
var xOriginal = Expression.Property(xParameter, mi);
// set value "Field1 = o.Field1"
return Expression.Bind(mi, xOriginal);
}
);
// initialization "new T { Field1 = o.Field1, Field2 = o.Field2 }"
var xInit = Expression.MemberInit(xNew, bindings);
// expression "o => new T { Field1 = o.Field1, Field2 = o.Field2 }"
var lambda = Expression.Lambda<Func<T, string>>(xInit, xParameter);
// compile to Func<T, string>
return lambda.Compile();
}
What I was wondering however is:
How do I build this as an expression / func that I can use with an IQueryable to do something like this
// this would build me a string array from the specified properties
// in a collection of T joining the values using the given separator
var results = data.Select(i => BuildString(fields, "|")).ToArray();
I would ideally like to use this with an entity set.
String conversion/concatenation is not a database job. You'd better keep the two parts separate - data retrieval in database query and data transformation in memory query.
For instance, you can use the following custom extensions methods:
public static class Extensions
{
public static IQueryable<T> Select<T>(this IQueryable source, string[] fields)
{
var parameter = Expression.Parameter(source.ElementType, "o");
var body = Expression.MemberInit(
Expression.New(typeof(T)),
fields.Select(field => Expression.Bind(
typeof(T).GetProperty(field),
Expression.PropertyOrField(parameter, field))
)
);
var selector = Expression.Lambda(body, parameter);
var expression = Expression.Call(
typeof(Queryable), "Select", new[] { parameter.Type, body.Type },
source.Expression, Expression.Quote(selector)
);
return source.Provider.CreateQuery<T>(expression);
}
public static IEnumerable<string> Serialize<T>(this IEnumerable<T> source, string separator)
{
var properties = typeof(T).GetProperties();
return source.Select(item => string.Join(separator, properties.Select(property => property.GetValue(item))));
}
}
like this
var results = db.Data.Select<ProjectionOfT>(fields).Serialize("|");
If you want to avoid the ProjectionOfT class, there is no easy way to do that since it requires dynamic runtime class generation, so you'd better resort to System.Linq.Dynamic package.

Using reflection to retrieve a value from a list

I have a simple method which retrieves a table from an azure mobile service.
public static async List<T>GetDataFromListTable<T>()
{
var data = await MobileService.GetTable<T>().ToListAsync();
return data.Count != 0 ? data : null;
}
This works fine.
What I am trying to do is have another method that takes a parameter name which is returned from the service and return the value of that parameter. So far I have this
public static async Task<T> GetDataFromTable<T>(string paramName)
{
var k = Activator.CreateInstance(typeof(T));
var members = typeof(T).GetProperties().Select(t=>t.Name).ToList();
if (!members.Contains(paramName))
return (T)k;
var mn = typeof(T).GetProperties()[members.IndexOf(paramName)];
var data = GetDataFromListTable<T>();
var retval = data.Select(t => t.mn);
}
The issue is obviously that I can't do the Linq query as T doesn't contain mn. I can also not use
var retval = data.Select(t=>t.paramName);
as paramname is a just a string representation of a member within a class.
In a nutshell...
method 1 has the parameter name, grabs a list from method 2. From the returned list in method 2, find the parameter name and return the associated value.
Is there a way to do what I'm trying to do?
You can do:
var retval = data.Select(t => mn.GetGetMethod().Invoke(t, null));
or
var retval = data.Select(t => mn.GetValue(t, null));
You can also simplify your code with something like this (not tested, sorry):
public static async Task<T> GetDataFromTable<T>(string paramName)
{
var k = Activator.CreateInstance(typeof(T));
var mn = typeof(T).GetProperty(paramName);
if (mn == null)
return (T)k;
var data = GetDataFromListTable<T>();
var retval = data.Select(t => mn.GetGetMethod().Invoke(t, null));
...
}
I think using expression trees would be more convenient since you're working with collections. Your method signature needs to incorporate the types T and TResult since it is using Select which returns an IEnumerable<TResult>.
public static async Task<IEnumerable<TResult>> SelectData<T, TResult>(
string propertyName
)
{
if(string.IsNullOrWhiteSpace(propertyName))
{
return Enumerable.Empty<TResult>();
}
var dataTask = GetTableData<T>();
var tType = Expression.Parameter(typeof(T), "t");
var property = Expression.Property(tType, propertyName);
var selectExpression =
Expression.Lambda<Func<T, TResult>>(property, tType)
.Compile();
return (await dataTask).Select(selectExpression);
}
Isn't it possible to do this
var retval = data.Select(t => mn.GetValue(t, null));

Exception with Custom expression builder with ICollection / IEnumerable

I have been trying to write a method that will build an expression based on types and parameters passed in. Currently the method is:
// tuple: CollectionName, ClassName, PropertyName
public Expression<Func<T, bool>> BuildCollectionWithLike<T, TSub>(Dictionary<Tuple<string, string, string>, string> properties)
{
// each one should generate something like:
// x => x.PreviousSKUs.Where(y => y.PreviousSku.Contains("a"))
try
{
var type = typeof(T);
List<Expression> expressions = new List<Expression>();
var xParameter = Expression.Parameter(typeof(T), "x");
foreach (var key in properties.Keys)
{
var collectionType = typeof(TSub);
var yParameter = Expression.Parameter(typeof(TSub), "y");
var propertyExp = Expression.Property(yParameter, key.Item3);
MethodInfo methodContains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(properties[key], typeof(string));
var containsMethodExp = Expression.Call(propertyExp, methodContains, someValue);
var whereProperty = type.GetProperty(key.Item1);
var wherePropertyExp = Expression.Property(xParameter, whereProperty);
Func<IEnumerable<T>, Func<T, bool>, IEnumerable<T>> whereDelegate = Enumerable.Where;
MethodInfo whereMethodInfo = whereDelegate.Method;
var whereMethodExp = Expression.Call(whereMethodInfo, wherePropertyExp, containsMethodExp);
expressions.Add(whereMethodExp);
}
Expression final = expressions.First();
foreach (var expression in expressions.Skip(1))
{
final = Expression.Or(final, expression);
}
Expression<Func<T, bool>> predicate =
(Expression<Func<T, bool>>)Expression.Lambda(final, xParameter);
return predicate;
}
catch (Exception ex)
{
return null;
}
}
However at this line:
var whereMethodExp = Expression.Call(whereMethodInfo, wherePropertyExp, containsMethodExp);
I get this exception:
Expression of type 'System.Collections.Generic.ICollection`1[Model.ProductPreviousSku]'
cannot be used for parameter of type 'System.Collections.Generic.IEnumerable`1[Model.Product]'
of method 'System.Collections.Generic.IEnumerable`1[Model.Product] Where[Product](System.Collections.Generic.IEnumerable`1[Model.Product], System.Func`2[Model.Product,System.Boolean])'"
My class Model.Product has a property of type ICollection called PreviousSKUs.
I have a class called ProductPreviousSku which has a property of type string called PreviousSku.
As per my comment in at the start of the method I am trying to get this method to be able to construct an expression inside the foreach loop that looks like:
x => x.PreviousSKUs.Where(y => y.PreviousSku.Contains("a"))
I'm struggling to get passed this error at the moment so any help would be fantastic !

how to get a Linq select value from dynamic?

I'm writing a method to let the client decide which fields they want to select from a table
here is what i do so far
public IList<User> List(int? roleId, int? sequence, string name,System.Linq.Expressions.Expression<Func<User, dynamic>> selector)
{
var query = context.Users.AsQueryable();
if (roleId.HasValue && roleId.Value > 0)
query = query.Where(x => x.RoleId == roleId);
if (sequence.HasValue && sequence.Value > 0)
query = query.Where(x => x.Sequence == sequence);
if (!string.IsNullOrEmpty(name))
query = query.Where(x => x.Name.Contains(name));
query = query.OrderBy(x => x.UserId);
var result = query.Select(selector).ToList();
var users = new List<User>();
User user = null;
foreach (var item in result)
{
user=new User();
user.UserId = item.id;
user.Name = item.name;
//user.Email = item.email;
//user.Sequence = item.sequence;
users.Add(user);
}
return users;
}
it compile an error says: item.id is not define, but i can see item{id=4,name="sam"....}
If you want to give ability for client code to choose, what fields it wants to retrieve from database, then why do you restrict return type to User?
Let the client code choose, which return type it should use:
public IList<T> List(int? roleId, int? sequence, string name,System.Linq.Expressions.Expression<Func<User, T>> selector)
{
// ...
query = query.OrderBy(x => x.UserId);
return query.Select(selector).ToList();
}
Otherwise, I can't imagine, how are you going to convert anything, returned from selector, into User instances.

Dynamic built Linq to SQL Query

i want to build a generic search window using linq to sql.
This is what i was trying to do:
class SearchWindow<T> : Form : Where T: class
{
public SearchWindow(Func<T, string> codeSelector,
Func<T, string> nameSelector)
{
var db = new DataContext();
var table = db.GetTable<T>();
var query = from item in table where
codeSelector(item).Contains(someText) &&
nameSelector(item).Contains(someOtherText)
select item;
}
}
And i was trying to use it like:
var searchWindow = new SearchWindow<SomeTable>(x => x.CodeColumn,
y => y.NameColumn).Show();
Bud saddly that doesn't work, i read about expression trees so i tried to do that with them, and i got:
public SearchWindow(codeColumn, nameColumn)
{
Table<T> table = db.GetTable<T>();
var instanceParameter = Expression.Parameter(typeof(T), "instance");
var methodInfo = typeof(string).GetMethod("Contains",
new Type[] { typeof(string) });
var codigoExpression = Expression.Call(Expression.Property(instanceParameter,
codeColumn),
methodInfo,
Expression.Constant("someText",
typeof(string)));
var nombreExpression = Expression.Call(Expression.Property(instanceParameter,
nameColumn),
methodInfo,
Expression.Constant("someOtherText",
typeof(string)));
var predicate = Expression.Lambda<Func<T, bool>>(
Expression.And(codigoExpression, nombreExpression), instanceParameter);
var query = table.Where(predicate);
}
And to use it i need to do:
new SearchWindow<SomeTable>("codeColumn", "nameColumn");
But i don't like the approach to need to enter the column names as a string, is there any way to do it in a fashion similar to my first approach (in order to have intellisense and strong typing)?
Thank you for your help.
Untested, but something like:
static IQueryable<T> Search<T>(
IQueryable<T> source,
Expression<Func<T, string>> codeSelector,
Expression<Func<T, string>> nameSelector,
string code, string name)
{
var row = Expression.Parameter(typeof(T), "row");
var body = Expression.AndAlso(
Expression.Call(
Expression.Invoke(codeSelector, row),
"Contains", null,
Expression.Constant(code, typeof(string))),
Expression.Call(
Expression.Invoke(nameSelector, row),
"Contains", null,
Expression.Constant(name, typeof(string))));
var lambda = Expression.Lambda<Func<T, bool>>(body, row);
return source.Where(lambda);
}
You pass in your table (GetTable<T>) as the source, and lambdas to indicate the columns (x => x.CodeColumn / y => y.NameColumn etc).
Update; tested on LINQ-to-Objects, I'm hopeful it'll work on LINQ-to-SQL as well:
var data = new[] {
new { Code = "abc", Name = "def"},
new { Code = "bcd", Name = "efg"},
new { Code = "ghi", Name = "jkl"}
}.AsQueryable();
var filtered = Search(data, x => x.Code, x => x.Name, "b", "f");
var arr = filtered.ToArray();
Use PredicateBuilder- it'll do the heavy lifting for you.

Categories