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

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();

Related

Linq Lambda Error : The specified LINQ expression contains references to queries that are associated with different contexts

I receive this error, and I tried a lot to solve it, but I receive other errors, is there a solution?
using (var contextDb1 = new Db1Context(System.Web.HttpContext.Current.Session["DB1ConnectionString"].ToString(), false))
{
using (var contextDb2 = new Db2Context(System.Web.HttpContext.Current.Session["DB2ConnectionString"].ToString(), false))
{
var messagesList = contextDb2.Messages
.Select(m => new MessagesViewModel
{
UserName = contextDb1.UsersInfo.FirstOrDefault(u=>u.Id == m.UserId).UserName,
MessageId = m.MessageId,
MessageText = m.MessageText,
DateTime = m.DateTime
})
.ToList();
return messagesList;
}
}
You cannot query two different databases via the same LINQ Query. But you can use intermediate result to execute two queries to databases and then combine result.
using (var contextDb1 = new Db1Context(System.Web.HttpContext.Current.Session["DB1ConnectionString"].ToString(), false))
using (var contextDb2 = new Db2Context(System.Web.HttpContext.Current.Session["DB2ConnectionString"].ToString(), false))
{
var rawMessages = contextDb2.Messages
.Select(m => new
{
m.UserId
m.MessageId,
m.MessageText,
m.DateTime
})
.ToList();
var userIds = rawMessages.Select(x => xu.UserId);
var usersInfo = contextDb1.UsersInfo.Where(u => userIds.Contains(u.Id))
.Select(u => new
{
UserId = u.Id,
UserName = u.UserName
});
var messageQuery =
from m in rawMessages
join u in usersInfo.AsEnumerable() on m.UserId equals u.UserId into gj
from u in gj.DefaultIfEmpty()
select new MessagesViewModel
{
UserName = u?.UserName,
MessageId = m.MessageId,
MessageText = m.MessageText,
DateTime = m.DateTime
};
var messagesList = messageQuery.ToList();
return messagesList;
}

Push multiple objects with dynamic data into a DTO and return it

So I'm trying to use a DTO to reshape and return data, it's not working because I'm trying to push in an array of objects (as an IQueryable - which I don't think works) into the DTO, I'm also trying to push in dynamic data into one of the properties, as seen below in the 'hasCurrentUserLiked' property. I need to figure out How to change the objects from IQueryable, into actual objects so they can all be pushed into the DTO and the dynamic data can be worked out.
This is the code
public async Task<PagedList<UserPhoto>> GetSpecificFeed(UserParams userParams)
{
var user = _context.Users.FirstOrDefault(x => x.Username == userParams.u);
var userId = user.Id;
var photos = _context.UserPhotos;
var followerIds = _context.Follows.Where(x => x.FollowerId == userId).Select(x => x.FollowingId).ToList();
var feeds = _context.UserPhotos.Where(x => followerIds.Contains(x.UserId)).OrderByDescending(x => x.DateAdded);
// this doesn't work because 'feeds' is an IQueryable, not an object
var like = await hasCurrentUserLiked(user.Id, feeds.id);
// this has the same problem as above
var feedsToReturn = new FeedsForReturnDto
{
Id = feeds.Id,
PhotoUrl = feeds.photoUrl,
Username = feeds.Username,
Description = feeds.Description,
DateAdded = feeds.DateAdded,
IsImage = feeds.IsImage,
hasCurrentUserLiked = like,
Likes = feeds.Likes
}
return await PagedList<UserPhoto>.CreateAsync(feedsToReturn, userParams.PageNumber, userParams.PageSize);
}
I thought that I might be able to get each image.id in a similar way the 'followerIds' are worked out but I couldn't figure out how to get this to work
[EDIT]
As per Enas Osamas answer, I've changed the code to this and I've debugged it, feedsToReturn has the correct info, so it is doing what its supposed to do. The problem I'm having now is that I'm unable to return it as it can't convert the IEnumerable to an IQueryable. I tried adding an explicit cast but that didn't work, I also tried removing the PagedList, and replacing the type to but this didn't work. My is an IQueryable which might be the problem, I tried changing that to an IEnumerable but this would mess up the code in other places.
This is my new code (still returning 'feeds' instead of 'feedsToReturn', will change when it works)
public async Task<PagedList<UserPhoto>> GetSpecificFeed(UserParams userParams)
{
var user = _context.Users.FirstOrDefault(x => x.Username == userParams.u);
var userId = user.Id;
var photos = _context.UserPhotos;
var followerIds = _context.Follows.Where(x => x.FollowerId == userId).Select(x => x.FollowingId).ToList();
var feeds = _context.UserPhotos.Where(x => followerIds.Contains(x.UserId)).OrderByDescending(x => x.DateAdded);
var feedToReturn = feeds.AsEnumerable().Select(feed => new FeedsForReturnDto
{
Id = feed.Id,
PhotoUrl = feed.photoUrl,
Username = feed.Username,
Description = feed.Description,
DateAdded = feed.DateAdded,
IsImage = feed.IsImage,
hasCurrentUserLiked = hasCurrentUserLiked(user.Id, feed.Id),
Likes = feed.Likes
});
return await PagedList<UserPhoto>.CreateAsync(feeds, userParams.PageNumber, userParams.PageSize);
}
I also tried changing some of the types around and this is what I came up with. The problem here is this:
'System.Collections.Generic.IEnumerable<cartalk.api.Dtos.feeds.FeedsForReturnDto>' to 'System.Linq.IQueryable<System.Collections.IEnumerable>'
and this problem is with the 'feedsToReturn' in the last line
public async Task<PagedList<IEnumerable>> GetSpecificFeed(UserParams userParams)
{
var user = _context.Users.FirstOrDefault(x => x.Username == userParams.u);
var userId = user.Id;
var photos = _context.UserPhotos;
var followerIds = _context.Follows.Where(x => x.FollowerId == userId).Select(x => x.FollowingId).ToList();
var feeds = _context.UserPhotos.Where(x => followerIds.Contains(x.UserId)).OrderByDescending(x => x.DateAdded);
var feedToReturn = feeds.AsEnumerable().Select(feed => new FeedsForReturnDto
{
Id = feed.Id,
PhotoUrl = feed.photoUrl,
Username = feed.Username,
Description = feed.Description,
DateAdded = feed.DateAdded,
IsImage = feed.IsImage,
hasCurrentUserLiked = hasCurrentUserLiked(user.Id, feed.Id),
Likes = feed.Likes
});
return await PagedList<IEnumerable>.CreateAsync(feedToReturn, userParams.PageNumber, userParams.PageSize);
}
PagedList code
public static async Task<PagedList<T>> CreateAsync(IQueryable<T> source,
int pageNumber, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
return new PagedList<T>(items, count, pageNumber, pageSize);
}
[EDIT]
This is the hasCurrentUserLiked function, it works here
public bool checkCurrentUserLiked(int currentUserId, int imageId)
{
var doesCurrentUserLike = _context.PhotoLikes.Where(x => x.LikerId == currentUserId && x.ImageId == imageId);
var value = true;
if (doesCurrentUserLike == null)
{
value = false;
}
else
{
value = true;
}
return value;
}
You can try something like
feeds.AsEnumerable().Select(feed => new FeedsForReturnDto
{
Id = feed.Id,
PhotoUrl = feed.photoUrl,
Username = feed.Username,
Description = feed.Description,
DateAdded = feed.DateAdded,
IsImage = feed.IsImage,
hasCurrentUserLiked = hasCurrentUserLiked(user.Id, feed.id),
Likes = feed.Likes
});
This would return an IEnumerable containing all the feeds mapped to your DTO after being enumerated to avoid performing operations against the database
[EDIT]
you can maybe do a left join with the _context.PhotoLikes.
Like this
feeds.GroupJoin(_context.PhotoLikes,
f => new { Id = f.Id, UserId = user.Id },
p => new { Id = p.ImageId, UserId = p.LikerId },
(f, p) => new { feed = f, photoLike = p })
.SelectMany(f => f.photoLike.DefaultIfEmpty(),
(f, p) => new FeedsForReturnDto
{
Id = f.feed.Id,
PhotoUrl = f.feed.photoUrl,
Username = f.feed.Username,
Description = f.feed.Description,
DateAdded = f.feed.DateAdded,
IsImage = f.feed.IsImage,
hasCurrentUserLiked = p != null,
Likes = feed.Likes
});
Just note that in this part
f => new { Id = f.Id, UserId = user.Id },
p => new { Id = p.ImageId, UserId = p.LikerId },
the datatypes of properties in both objects must match or it won't compile

(MVC) Trying to join a table it self by LinQ for display multiple language

I am trying to use LinQ to join a table itself, in order to display different columns in different session. In this case is when the LanguageId is "en" the "EnglishName" columns should be assigned to "Name". It always shows the error "cannot implicitly convert type...". I dont' know how to fix this code. Pls see the code bellow and help. Many tks.
MemberDao
public List<MemberViewModel> ListNewMember(int top, string languageId)
{
if (languageId == "en")
{
var model1 = from a in db.Members
join b in db.Members
on a.MemberId equals b.MemberId
select new MemberViewModel()
{
MemberId = a.MemberId,
Name = a.EnglishName,
Status = a.Status,
CreatedDate = a.CreatedDate
};
IQueryable<MemberViewModel> model2 = model1;
model2 = model2.Where(x => x.Status == true).OrderByDescending(x => x.CreatedDate).Take(top).ToList();
}
return db.Members.Where(x => x.Status == true).OrderByDescending(x => x.CreatedDate).Take(top).ToList();
}
MemberController
using GBVNET.Common;
using Model.Dao;
using Models.Dao;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace GBVNET.Controllers
{
public class MemberController : BaseClientController
{
//[ChildActionOnly]
//[OutputCache(Duration = 3600 * 24)]
public ActionResult Index()
{
var slideDao = new SlideDao();
var currentCulture = Session[CommonConstants.CurrentCulture].ToString();
ViewBag.ChildrenSlides = slideDao.ListChildrenSlide(1, currentCulture);
var categoryDao = new CategoryDao();
ViewBag.DocumentCategory = categoryDao.DocumentCategory(1, currentCulture);
ViewBag.ContactCategory = categoryDao.ContactCategory(1, currentCulture);
var model = new MemberDao().ListNewMember(5, currentCulture);
return View(model);
}
public ActionResult Detail(int id)
{
var slideDao = new SlideDao();
var currentCulture = Session[CommonConstants.CurrentCulture].ToString();
ViewBag.ChildrenSlides = slideDao.ListChildrenSlide(1, currentCulture);
var categoryDao = new CategoryDao();
ViewBag.DocumentCategory = categoryDao.DocumentCategory(1, currentCulture);
ViewBag.ContactCategory = categoryDao.ContactCategory(1, currentCulture);
var member = new MemberDao().ViewDetail(id);
return View(member);
}
}
}
This is the screenshot of the error: https://i.stack.imgur.com/q9qB2.jpg
I fixed them all by change the code as bellow:
MemberDao
public List<MemberViewModel> ListNewMember(int top, string languageId)
{
if (languageId == "en")
{
IEnumerable<MemberViewModel> model = from a in db.Members
join b in db.Members
on a.MemberId equals b.MemberId
select new MemberViewModel()
{
MemberId = a.MemberId,
Name = b.EnglishName,
Status = a.Status,
Logo = a.Logo,
Email = a.Email,
Address = a.Address,
Banner = a.Banner,
Facebook = a.Facebook,
Description = b.EnglishDescription
};
model = model.Where(x => x.Status == true).OrderByDescending(x => x.CreatedDate).Take(top).ToList();
return model.Where(x => x.Status == true).OrderByDescending(x => x.CreatedDate).Take(top).ToList();
}
else
{
IEnumerable<MemberViewModel> model = from a in db.Members
join b in db.Members
on a.MemberId equals b.MemberId
select new MemberViewModel()
{
MemberId = a.MemberId,
Name = b.Name,
Status = a.Status,
Logo = a.Logo,
Email = a.Email,
Address = a.Address,
Banner = a.Banner,
Facebook = a.Facebook,
Description = a.Description
};
model = model.Where(x => x.Status == true).OrderByDescending(x => x.CreatedDate).Take(top).ToList();
return model.Where(x => x.Status == true).OrderByDescending(x => x.CreatedDate).Take(top).ToList();
}
}

How to custom response in asp.net web api2

the problem in this function when add this two lines.
UserImage = GetImagePath(db.Users.FirstOrDefault(x => x.Id == p.User_ID).Image),
InsertDate = p.InsertDate.ToString("dd/MM/yyyy")
I need to custom Image Path and custom date format,show this problem.
LINQ to Entities does not recognize the method System.String ToString(System.String) method, and this method cannot be translated into a store expression
private static string GetImagePath(string ImageName)
{
return System.Configuration.ConfigurationManager.AppSettings["websiteurl"].ToString() + "/uploads/" + ImageName;
}
[HttpGet]
[Route("Comments")]
public IHttpActionResult Comments(string Post_ID)
{
var list = db
.ShalehComments
.Where(p => p.Shaleh_ID == Post_ID && p.ParentID == 0)
.Select(p => new {
ID = p.ID,
Comment = p.Comment,
User = db.Users.FirstOrDefault(x=> x.Id == p.User_ID).FullName,
UserImage = GetImagePath(db.Users.FirstOrDefault(x => x.Id == p.User_ID).Image),//here problem
InsertDate = p.InsertDate.ToString("dd/MM/yyyy")//and here
}).ToList();
return Ok(
new
{
result = true,
data = list
}
);
}
This error occurred because Entity Framework does not know how to execute .ToString() or GetImagePath method in sql. So you should load the data by using ToList.So you try something like:
[HttpGet]
[Route("Comments")]
public IHttpActionResult Comments(string Post_ID)
{
var list = db
.ShalehComments
.Where(p => p.Shaleh_ID == Post_ID && p.ParentID == 0)
.ToList()//add ToList
.Select(p => new {
ID = p.ID,
Comment = p.Comment,
User = db.Users.FirstOrDefault(x=> x.Id == p.User_ID).FullName,
UserImage = GetImagePath(db.Users.FirstOrDefault(x => x.Id == p.User_ID).Image),
InsertDate = p.InsertDate.ToString("dd/MM/yyyy")//and here
}).ToList();
return Ok(
new
{
result = true,
data = list
}
);
}

Select List of Object Which Contain Another List by LINQ

I have three database model which are shown below
I have two DTO class which are shown below
class RoleDTO
{
string RoleId;
string EnglishName;
Guid TypeId;
List<ClaimDTO> claims;
}
class ClaimDTO
{
string ActionID;
string ActionCode;
string ActionLevel;
string GrantDate;
}
Now I want to retrieve List of RoleDTO object from the database. So far I tried
public List<RoleDTO> GetRoleByType(Guid roleTypeId)
{
var roleDTOs = (from r in ctx.Roles
join rc in ctx.RoleClaims on r.RoleID equals rc.RoleID
join a in ctx.Actions on rc.ActionID equals a.ActionID
where r.RoleTypeID == roleTypeId
select new RoleDTO
{
RoleId = r.RoleID,
EnglishName = r.EnglishName,
TypeId = r.TypeID,
claims = List of ClaimDTO objects related to this role
}).ToList();
return roleDTOs;
}
My question is how can I retrieve list of ClaimDTO objects inside select statement. Is my linq correct?
I am using Telerik OpenAccess as ORM.
Below change should help to get the results
public List<RoleDTO> GetRoleByType(Guid roleTypeId)
{
var roleDTOs = (from r in ctx.Roles
join rc in ctx.RoleClaims on r.RoleID equals rc.RoleID
where r.RoleTypeID == roleTypeId
select new RoleDTO
{
RoleId = r.RoleID,
EnglishName = r.EnglishName,
TypeId = r.TypeID,
claims = ctx.Actions.Where( c => c.ActionId == rc.ActionId).Select( s => new ClaimDTO
{
ActionID = s.ActionID,
ActionCode = s.ActionCode,
ActionLevel = s.ActionLevel,
GrantDate = s.GrantDate
})ToList()
}).ToList();
return roleDTOs;
}
Another Alternative
List<ClaimDTO> claimsList = ctx.Actions.Select( s => new ClaimDTO
{
ActionID = s.ActionID,
ActionCode = s.ActionCode,
ActionLevel = s.ActionLevel,
GrantDate = s.GrantDate
})ToList();
var roleDTOs = ctx.Roles.Join(ctx.RoleClaims, r => r.RoleID, rc => rc.RoleID, (r,rc) => new
{
r,rc
}).Where( r => r.RoleTypeID == roleTypeId)
.Select( row => new RoleDTO
{
RoleID = row.r.RoleID,
EnglishName = row.r.EnglishName,
TypeID = row.r.TypeID,
claims = claimsList.Where( c => c.ActionId == rc.ActionId)
}).ToList();
If you use Include method then below query could help
var roleDTOs = ctx.Roles.Include("RoleClaims").Join(ctx.Actions, r => r.RoleClaims.Select(rc => rc.ActionID).FirstOrDefault() , a => a.actionid , (r,a) => new
{
r,a
}.Where(r => r.RoleTypeID == roleTypeId)
.Select( row => new RoleDTO
{
RoleID = row.r.RoleID,
EnglishName = row.r.EnglishName,
TypeId = row.r.RoleTypeID,
Claims = row.a.Select( c => new ClaimDTO
{
ActionID = c.ActionID,
ActionCode = c.ActionCode,
ActionLevel = c.ActionLabel,
GrantDate = row.r.RoleClaims.Select( g => g.grantDate)
})
}).ToList();

Categories