how to get a Linq select value from dynamic? - c#

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.

Related

How to dynamically query IQueryable array by each property using reflection?

I have search method with gets as parameter some DTO and make where filtering dynamically by DTO properties. The problem I get when using reflection is that I should explicitly cast to a property type that I am using. Is there a way to make it dynamic too so that the method that that make filtering can work without knowing type of a property. I am using Dynamic Ling Library.
CODE:
public async Task<ServiceResponceWithData<List<SearchResponseDTO>>> SearchAsync(SearchDTO model)
{
var users = from user in _userManeger.Users
join ur in _context.UserRoles
on user.Id equals ur.UserId
select new SearchResponseDTO
{
UserName = user.UserName,
Email = user.Email,
PhoneNumber = user.PhoneNumber,
FirstName = user.FirstName,
LastName = user.LastName,
Roles = _roleManager.Roles
.Where(c => c.Id == ur.RoleId)
.Select(c => c.Name)
.ToList()
};
var startResult = users;
var props = typeof(SearchDTO).GetProperties();
foreach (var prop in props)
{
if(prop.Name != "Role")
{
users = FilterWithWhereByStringTypeProperty(users, prop, model);
}
else
{
if (model.Role != null)
{
var results = users.Where(c => c.Roles.Any(r => r == model.Role));
users = results.Any() ? results : users;
}
}
}
var endResultList = startResult == users ? new List<SearchResponseDTO>() :await users.ToListAsync();
return new ServiceResponceWithData<List<SearchResponseDTO>>() { Data = endResultList, Success = true };
}
private IQueryable<SearchResponseDTO> FilterWithWhereByStringTypeProperty(IQueryable<SearchResponseDTO> collection,
PropertyInfo property,SearchDTO model )
{
var propertyName = property.Name;
var modelPropVal = property.GetValue(model);
if (modelPropVal == null) return collection;
string val = (string)modelPropVal;
string condition = String.Format("{0} == \"{1}\"", propertyName, val);
var fillteredColl = collection.Where(condition);
return fillteredColl.Any() ? fillteredColl : collection;
}
}
You could use System.Linq.Dynamic.Core.
In that case your code could be like (not tested):
var users = from user in _userManeger.Users
join ur in _context.UserRoles on user.Id equals ur.UserId
select new SearchResponseDTO
{
UserName = user.UserName,
Email = user.Email,
PhoneNumber = user.PhoneNumber,
FirstName = user.FirstName,
LastName = user.LastName,
Roles = _roleManager.Roles
.Where(c => c.Id == ur.RoleId)
.Select(c => c.Name)
.ToList()
};
IQueryable query = users;
var props = typeof(SearchDTO).GetProperties();
foreach (var prop in props)
{
if (prop.Name != "Role")
{
query = query.Where($"{prop.Name} == #0", prop.GetValue(model, null));
}
else
{
if (model.Role != null)
{
query = users.Where(c => c.Roles.Any(r => r == model.Role));
}
}
}
return query.ToList();

Change orderby depending on value of var

I have a foreach with an ordebydescending(), but in one case I need to use an orderby() instead. Depending on the value of articleType how can I use an inline condition inside the foreach to allow this to happen.
This is the condition I need to build into to determine the use of orderbydescending or orderby
if (articleType == BusinessLogic.ArticleType.Webinar)
This is the full function
public static List<Article> GetArticles(BusinessLogic.ArticleType articleType, long languageID)
{
List<Article> articles = new List<Article>();
using (var db = new DatabaseConnection())
{
foreach (var record in db
.GetArticles(BusinessLogic.SystemComponentID, (int) articleType, languageID)
.OrderByDescending(c => c.Date_Time))
{
#region articleTextLanguageID
long articleTextLanguageID = GetArticleTextLanguageID(record.ID, languageID);
string previewImageName = GetArticle(record.ID, articleTextLanguageID).PreviewImageName;
#endregion
Article article = new Article()
{
ID = record.ID,
Title = record.Title,
Summary = record.Summary,
PreviewImageName = previewImageName,
Date = record.Date_Time,
ArticleTextLanguageID = articleTextLanguageID
};
articles.Add(article);
}
}
return articles;
}
Was thinking something along these lines, but its not working
foreach (var record in db
.GetArticles(BusinessLogic.SystemComponentID, (int)articleType, languageID)
.Where(articleType == BusinessLogic.ArticleType.Webinar.ToString()?.OrderByDescending(c => c.Date_Time)) : .OrderBy(c => c.Date_Time)))
The way to do this would be to construct the query in pieces. For example:
var query = db.GetArticles(BusinessLogic.SystemComponentID, (int)articleType, languageID);
if(articleType == BusinessLogic.ArticleType.Webinar)
{
query = query.OrderByDescending(c => c.Date_Time);
}
else
{
query = query.OrderBy(c => c.Date_Time);
}
foreach(var record in query)
{
// process
}
If you required additional sorting, you'd need an extra variable typed as IOrderedEnumerable/IOrderedQueryable (depending on what GetArticles returns) as an intermediate to chain ThenBy/ThemByDescending:
var source = db.GetArticles(BusinessLogic.SystemComponentID, (int)articleType, languageID);
IOrderedEnumerable<Article> query;
if(articleType == BusinessLogic.ArticleType.Webinar)
{
query = source.OrderByDescending(c => c.Date_Time);
}
else
{
query = source.OrderBy(c => c.Date_Time);
}
if(somethingElse)
{
query = query.ThenBy(c => c.OtherProperty);
}
foreach(var record in query)
{
// process
}
Based on your comment below, as notes above the second example would look more like the following (this means that db.GetArticles returns an IQueryable<Article> and not an IEnumerable<Article>):
var source = db.GetArticles(BusinessLogic.SystemComponentID, (int)articleType, languageID);
IOrderedQueryable<Article> query;
if(articleType == BusinessLogic.ArticleType.Webinar)
{
query = source.OrderByDescending(c => c.Date_Time);
}
else
{
query = source.OrderBy(c => c.Date_Time);
}
if(somethingElse)
query = query.ThenBy(c => c.OtherProperty);
foreach(var record in query)
{
// process
}
You could also shorten it to the following:
var source = db.GetArticles(BusinessLogic.SystemComponentID, (int)articleType, languageID);
var query = articleType == BusinessLogic.ArticleType.Webinar
? source.OrderByDescending(c => c.Date_Time)
: source.OrderBy(c => c.Date_Time);
if(somethingElse)
query = query.ThenBy(c => c.OtherProperty);
foreach(var record in query)
{
// process
}

C# Linq query to return Dictionary<int,int[]>

Ok, so I have a need to create/return a Dictionary from the results of a Linq Query. I have tried just about everything I can think of and keep running into issues. Here is what I am currently attempting...
public static Dictionary<int,int[]> GetEntityAuthorizations(int userId)
{
using (MyDb db = new MyDb())
{
var query = db.EntityManagerRoleAssignments.Where(x => x.EntityManager.ManagerId == userId);
var entityId = query.Select(x => x.EntityManager.EntityId);
var roles = query.Select(x => x.RoleId).ToArray();
var result = query.ToDictionary(entityId, roles);
return result;
}
}
any help at all would be greatly appreciated. what i am looking for to be returned from this is a Dictionary where the Key is the entityId or EntityManager.EntityId and the Value(s) are an array of associated RoleId's.
Currently I am getting the following two errors at compile time, and other attempts have been errors similar but not exact to these.
Error 11 The type arguments for method 'System.Linq.Enumerable.ToDictionary<TSource,TKey>(System.Collections.Generic.IEnumerable, System.Func<TSource,TKey>, System.Collections.Generic.IEqualityComparer<TKey>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
Error 12 Cannot implicitly convert type 'System.Collections.Generic.Dictionary<TKey,Sqor.Database.DbEntityManagerRoleAssignment>' to 'System.Collections.Generic.Dictionary<int,int[]>'
UPDATE - Working Solution (Thanks to #D Stanley)
public static Dictionary<int,int[]> GetEntityAuthorizations(int userId)
{
using (SqorDb db = new SqorDb())
{
var query = db.EntityManagerRoleAssignments.Where(x => x.EntityManager.ManagerId == userId);
var entityGroups = query.GroupBy(x => x.EntityManager.EntityId);
var result = entityGroups.ToDictionary(e => e.Key,
g => g.Select(x => x.RoleId).ToArray()
);
return result;
}
}
It sounds like you want to group by the Entity ID and project the associated role IDs to an array:
using (MyDb db = new MyDb())
{
var query = db.EntityManagerRoleAssignments.Where(x => x.EntityManager.ManagerId == userId);
var entityGroups = query.GroupBy(x => x.EntityManager.EntityId);
var result = entityGroups.ToDictionary(e => e.Key,
g => g.Select(x => x.RoleId).ToArray()
);
return result;
}

Getting Results With Entity Framework

I'm trying to query a db set, but I can't seem to access the data when I get the results. Here is the code I've tried.
Query #1:
public List<CustomerMain> testing()
{
var context = new CustomerEntities();
var query = context.CustomerMains
.Where(p=> p.FirstName == "Thor")
.Select(p => new CustomerMain {FirstName=p.FirstName,LastName =p.LastName}).ToList();
return query;
}
Query #2:
public IQueryable<CustomerMain> testing()
{
var context = new CustomerEntities();
var query = context.CustomerMains
.Where(p=> p.FirstName == "Thor")
.Select(p => new CustomerMain {FirstName=p.FirstName,LastName =p.LastName});
return query;
}
CustomerMain is my DbSet, If I run either of those and assign the method to a var variable, it gives me no options to grab the FirstName or LastName from the variable. I found I can do it if I convert it to a CustomerMain, but shouldn't the return query be in a CustomerMain type already?
it gives me no options to grab the FirstName or LastName from the variable
Because the method returns a collection or queryable, not a single entity. You need to iterate or select records:
var customers = testing();
foreach (CustomerMain customer in customers)
{
// access customer.FirstName
}
Or:
var customers = testing();
var oneCustomer = customers.FirstOrDefault(c => c.FirstName == "Thor");
// access oneCustomer.FirstName
Or, if you want the method to return one record (or null if none found):
public CustomerMain FindOne(string firstName)
{
using (var context = new CustomerEntities())
{
var query = context.CustomerMains.Where(p => p.FirstName == firstName);
return query.FirstOrDefault();
}
}

What causes the Linq error: This method cannot be translated into a store expression?

I have a bunch of Linq to Entity methods that had the same select statement, so I thought I would be clever and separate that out into it's own method to reduce redundancy... but when i attempted to run the code, i got the following error...
this method cannot be translated into
a store expression
Here is the method i created...
public User GetUser(DbUser user, long uid)
{
return new User
{
Uid = user.uid,
FirstName = user.first_name,
LastName = user.last_name
};
}
And am calling in a method like this...
public User GetUser(long uid)
{
using (var entities = new myEntities()) {
return
entities.DbUsers.Where( x => x.uid == uid && x.account_status == ( short )AccountStatus.Active ).
Select( x => GetUser( x, uid ) ).FirstOrDefault( );
}
}
UPDATE: here is the code that works inline
public User GetUser(long uid, long uid_user)
{
using (var entities = new myEntities())
{
var q = from u in entities.DbUsers
where u.uid == uid_user
select new User
{
Uid = u.uid,
FirstName = u.first_name,
LastName = u.last_name,
BigPicUrl = u.pic_big,
Birthday = u.birthday,
SmallPicUrl = u.pic_small,
SquarePicUrl = u.pic_square,
Locale = u.locale.Trim(),
IsFavorite = u.FavoriteFriends1.Any(x => x.uid == uid),
FavoriteFriendCount = u.FavoriteFriends.Count,
LastWishlistUpdate = u.WishListItems.OrderByDescending(x => x.added).FirstOrDefault().added,
Sex = (UserSex)u.sex
};
var user = q.FirstOrDefault();
user.DaysUntilBirthday = user.Birthday.DaysUntilBirthday();
return user;
}
}
The error is spot on, you can't translate that into a T-SQL (or P-SQL) query.
You need to make sure you've executed the query before you attempt to hydrate it into some other type.
Keep it simple, use an extension method. That's what they are there for.
public static User ToUserEntity(this DbUser user)
{
return new User
{
Uid = user.uid,
FirstName = user.first_name,
LastName = user.last_name
};
}
Then in your DAL:
public User GetUser(long uid)
{
User dbUser;
using (var entities = new myEntities())
{
dbUser = entities.DbUsers
.Where( x => x.uid == uid && x.account_status == (short)AccountStatus.Active )
.FirstOrDefault(); // query executed against DB
}
return dbUser.ToUserEntity();
}
See how i hydrate the POCO into an object after the context has been disposed? This way, you ensure EF has finished it's expression work before you attempt to hydrate into a custom object.
Also i dont know why you're passing uid to that method, it's not even being used.
On a further note, you shouldn't need to do this kind of thing (project EF POCO's into your own objects).
If you do, it's a good case for custom POCO's (map the tables straight into your custom POCO's, don't use the Code Generation).
This expression will work to give the desired result (somewhat) I still havent figured out how to pass in additional variables in teh select statements...
..... .Select(GetUser).FirstOrDefault()
static readonly Expression<Func<DbUser, User>> GetUser = (g) => new User {
Uid = g.uid,
FirstName = g.first_name,
LastName = g.last_name,
BigPicUrl = g.pic_big,
Birthday = g.birthday,
SmallPicUrl = g.pic_small,
SquarePicUrl = g.pic_square,
Locale = g.locale.Trim(),
//IsFavorite = g.FavoriteFriends1.Any(x=>x.uid==uid),
FavoriteFriendCount = g.FavoriteFriends.Count,
LastWishlistUpdate = g.WishListItems.OrderByDescending( x=>x.added ).FirstOrDefault().added
};
You can't do this because the getUser method cannot be converted to any TSQL statement.
if you return your DBUser first and then use it as the first parameter of the GetUser method then you are forcing it to execute and once you have you DBUser you can pass it to GetUser
Maybe you can try this:
public User GetUser(long uid)
{
using (var entities = new myEntities())
{
return GetUser(
entities.DbUsers
.Where( x => x.uid == uid && x.account_status == (short)AccountStatus.Active )
.FirstOrDefault(),
uid);
}
}
EDIT
Since you are saying it still fails could it be beacuse of the enum??
public User GetUser(long uid)
{
using (var entities = new myEntities())
{
short status = (short)AccountStatus.Active;
return GetUser(
entities.DbUsers
.Where( x => x.uid == uid && x.account_status == status )
.FirstOrDefault(),
uid);
}
}

Categories