LINQ IQueryable Query - c#

Here's my query for retrieving list of inactive/active users from my database. my problem is that, my query seems to be too complicated. so can you please give me a tip how to enhance my query.
here's my code
using (basecampcoreEntities dbs = ConfigAndResource.BaseCampContext())
{
//loads all user where isactive property has the same value as IsActive
var Users = from useritem in dbs.users
where useritem.useraccount.IsActive.Equals(IsActive)
orderby useritem.useraccount.CreatedDate
select useritem;
//check if users count is greater than 0
if (Users.Count() > 0)
{
List<user> CasebookUser = new List<user>();
switch (SearchBy)
{
case DTO::SearchBy.FirstName:
{
CasebookUser = Users.Where(item => item.FirstName.ToUpper().Equals(SearchText.ToUpper())).Skip(skip).Take(take).ToList();
} break;
case DTO::SearchBy.LastName:
{
CasebookUser = Users.Where(item => item.LastName.ToUpper().Equals(SearchText.ToUpper())).Skip(skip).Take(take).ToList();
} break;
case DTO::SearchBy.LoginID:
{
CasebookUser = Users.Where(item => item.LoginID.ToUpper().Equals(SearchText.ToUpper())).Skip(skip).Take(take).ToList();
} break;
case DTO::SearchBy.None:
CasebookUser = Users.Skip(skip).Take(take).ToList();
{
} break;
}
//transform the data into DTO class
return (from item in CasebookUser
select new DTO::User
{
LoginID = item.LoginID,
FirstName = item.FirstName,
LastName = item.LastName,
MiddleName = item.MiddleName,
Birhtday = item.userinfo != null ? item.userinfo.Birthday : DateTime.UtcNow
}).ToList();
}
return null;

Leverage the power of delayed execution here...
// first filter by whether user is active or not
var query = dbs.users.Where(x => x.useraccount.IsActive == IsActive);
// next filter by specific search field
switch (SearchBy)
{
case DTO::SearchBy.FirstName:
{
query = query.Where(x => string.Equals(x.FirstName, SearchText, StringComparison.InvariantCultureIgnoreCase));
break;
}
case DTO::SearchBy.LastName:
{
query = Users.Where(x => string.Equals(x.LastName, SearchText, StringComparison.InvariantCultureIgnoreCase));
break;
}
...
}
// then apply paging
query = query.Skip(skip).Take(take);
// finally, order by CreatedDate (ascending)
query = query.OrderBy(x => x.useraccount.CreatedDate);
// now fetch the records!
return (from item in query
select new DTO::User
{
LoginID = item.LoginID,
FirstName = item.FirstName,
LastName = item.LastName,
MiddleName = item.MiddleName,
Birhtday = item.userinfo != null ? item.userinfo.Birthday : DateTime.UtcNow
}).ToList();
This code will give you what you need in a more optimal way (only 1 DB trip) - and it's a bit more readable into the bargain.

I would:
Remove the sort from the original query definition
Do not convert ToList() and to Skip()' andTake()` prematurely
Do not cast ToList() twice. Only do it when you are creating your final collection.
Make IsActive comparison more clear in the initial query
Rewrite first query as one line linq expression
Use Any() instead of Count() > 0
Convert your search text ToUpper() just one time. Makes your search cases more concise and readable (same with using == instead of Equals)
This code might help:
using (basecampcoreEntities dbs = ConfigAndResource.BaseCampContext())
{
//loads all user where isactive property has the same value as IsActive
var Users = db.Users.Where(x => x.useraccount.IsActive == IsActive);
if (Users.Any())
{
var searchText = SearchText.ToUpper();
switch (SearchBy)
{
case DTO::SearchBy.FirstName:
Users = Users.Where(item => item.FirstName.ToUpper() == searchText);
break;
case DTO::SearchBy.LastName:
Users = Users.Where(item => item.LastName.ToUpper() == searchText);
break;
case DTO::SearchBy.LoginID:
Users = Users.Where(item => item.LoginID.ToUpper() == searchText);
break;
}
// apply sort and skip/take
Users = Users.OrderBy(x => x.useraccount.CreateDate).Skip(skip).Take(take);
//transform the data into DTO class
return (from item in Users
select new DTO::User
{
LoginID = item.LoginID,
FirstName = item.FirstName,
LastName = item.LastName,
MiddleName = item.MiddleName,
Birthday = item.userinfo != null ? item.userinfo.Birthday : DateTime.UtcNow
}).ToList();
}
return null;
}

Related

how to put a string value from one Application Db Context to another?

I'v declared a var to select elements from db context named BUGS(Table).
i set it to include all the columns, there is a foreign key named ProjectsPId.
It can return the id (The foreign key), but it returns "null" if we call projects.name for example.
So, i declared a var ("BUGS1" = context.bugs) this give me the value of project.name with no problems.
How i can put the "Project Name" value from the var bugs1 to the var bugs.
i'v tried to do this:
bugs.Where(b => b.Projects.PName) = bugs1.Where(c => c.Projects.PName);
this give me an error
public async Task<IActionResult> Index(int? id, string sortOrder, string searchString)
{
var bugs1 = _context.BugsSummary;
var bugs = from b in _context.BugsSummary
select b;
{
if (!String.IsNullOrEmpty(searchString))
{
bugs = bugs.Where(b => b.Projects.PName.Contains(searchString)
|| b.Bug.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
bugs = bugs.OrderByDescending(b => b.Projects.PName);
break;
case "Date":
bugs = bugs.OrderBy(b => b.PublicationDate);
break;
case "date_desc":
bugs = bugs.OrderByDescending(b => b.PublicationDate);
break;
default:
bugs = bugs.OrderBy(b => b.Projects.PName);
break;
}
if (id > 0)
{
bugs.Include(b => b.User).Include(b => b.Projects)
.Where(b => b.ProjectsPId == id)
;
}
else
{
bugs.Include(b => b.User).Include(b => b.Projects);
}
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewData["CurrentFilter"] = searchString;
}
bugs.Where(b => b.Projects.PName) = bugs1.Where(c => c.Projects.PName);
return View(await bugs.AsNoTracking().ToListAsync());
}
When you set the initial value of the query, you need to use Include method to load related data like below :
var bugs = from b in _context.BugsSummary.Include(b=>b.Projects)
select b;
Then you don't need bug1 to pass the value of the string "project.name" to bugs

LINQ returns different sort order for same function

On Page_Load I set my GridView with: myGridView.DataSource = context.Table.OrderByDescending(x => x.Date).ToList();. Then I save the SortBy and SortDirection values in the ViewState with: ViewState["SortBy"] = "Date" and ViewState["SortDirection"] = "DESC". If I run SELECT * FROM Table Order By Date DESC I get the same entries in the same order as the GridView (sounds dumb that I pointed that out, but bear with me).
I also have a button that filters the GridView based on some TextBoxes. Let's say I have tbName, tbType, tbFrom, and tbTo. It's code goes like this:
protected void btnRefresh_Click(object sender, EventArgs e)
{
try
{
using (GasMarketerDBEntities context = new GasMarketerDBEntities())
{
if (context.Table.Count() > 0)
{
DateTime date;
List<Table> results = context.Table.ToList();
if (!string.IsNullOrEmpty(tbName.Text))
{
results = results.Where(x => x.Name == tbName.Text).ToList();
}
if (!string.IsNullOrEmpty(tbType.Text))
{
results = results.Where(x => x.Type == tbType.Text).ToList();
}
if (!string.IsNullOrEmpty(tbFrom.Text) && DateTime.TryParse(tbFrom.Text, out date))
{
results = results.Where(x => x.Date >= date).ToList();
}
if (!string.IsNullOrEmpty(tbTo.Text) && DateTime.TryParse(tbTo.Text, out date))
{
results = results.Where(x => x.Date <= date).ToList();
}
if (myGridView.EditIndex != -1)
{
//GridView is in Edit Mode
myGridView.EditIndex = -1;
}
if (ViewState["SortDirection"].ToString() == "ASC")
{
switch (ViewState["SortBy"].ToString())
{
case "Date":
results = results.OrderBy(x => x.Date).ToList();
break;
case "Name":
results = results.OrderBy(x => x.Name).ToList();
break;
case "Type":
results = results.OrderBy(x => x.Type).ToList();
break;
}
}
else
{
switch (ViewState["SortBy"].ToString())
{
case "Date":
results = results.OrderByDescending(x => x.Date).ToList();
break;
case "Name":
results = results.OrderByDescending(x => x.Name).ToList();
break;
case "Type":
results = results.OrderByDescending(x => x.Type).ToList();
break;
}
}
gvNominations.DataSource = results;
}
else
{
gvNominations.DataSource = null;
}
}
gvNominations.DataBind();
Session["DataSource"] = gvNominations.DataSource;
}
catch (Exception ex)
{
if (ex.InnerException != null)
{
while (ex.InnerException != null)
{
ex = ex.InnerException;
}
}
((System.Web.UI.HtmlControls.HtmlGenericControl)Master.FindControl("errorMessage")).InnerText = "[btnRefresh_Click]: " + ex.Message;
}
}
}
If I don't put any values in the filter boxes and click Refresh it falls past all those if checks and gets to the sorting section which, having been unchanged, should run as Date DESC. And it does. The strange part is as soon as the results = results.OrderByDescending(x => x.Date).ToList(); line runs (so the same Where clause as when we set results at List<Table> results = context.Table.OrderByDescending(x => x.Date).ToList()) the order is not the same. The latest entries are at the top, but it appears to sort on a different second field.
Edit:
removed the OrderByDescending in the setting of results at the start of btnRefresh_Click. If I have that it seems to work, but I am wondering why I can't start with the same dataset, then apply the OrderByDescending later in the switch block and come out with the same order as when it's set with OrderByDescending in the Page_Load
One significant point about your example. Avoid using .ToList() too early in your query. Build up the IQueryable then execute it with ToList() once all of your filters and ordering are applied. This is a big code-smell with EF that leads to performance, and excessive memory use bugs that people wrongly attribute to Entity Framework.
For instance:
List<Table> results = context.Table.ToList();
This basically loads "SELECT * FROM Table" from the database into memory. Instead, build up your query then execute once at the end. This reduces the data being sent back from the server.
using (GasMarketerDBEntities context = new GasMarketerDBEntities())
{
DateTime date;
var query = context.Table.AsQueryable;
if (!string.IsNullOrEmpty(tbName.Text))
results = results.Where(x => x.Name == tbName.Text);
if (!string.IsNullOrEmpty(tbType.Text))
results = results.Where(x => x.Type == tbType.Text);
if (!string.IsNullOrEmpty(tbFrom.Text) && DateTime.TryParse(tbFrom.Text, out date))
results = results.Where(x => x.Date >= date);
if (!string.IsNullOrEmpty(tbTo.Text) && DateTime.TryParse(tbTo.Text, out date))
results = results.Where(x => x.Date <= date);
if (myGridView.EditIndex != -1)
myGridView.EditIndex = -1;
if (ViewState["SortDirection"].ToString() == "ASC")
{
switch (ViewState["SortBy"].ToString())
{
case "Date":
results = results.OrderBy(x => x.Date);
break;
case "Name":
results = results.OrderBy(x => x.Name);
break;
case "Type":
results = results.OrderBy(x => x.Type);
break;
}
}
else
{
switch (ViewState["SortBy"].ToString())
{
case "Date":
results = results.OrderByDescending(x => x.Date);
break;
case "Name":
results = results.OrderByDescending(x => x.Name);
break;
case "Type":
results = results.OrderByDescending(x => x.Type);
break;
}
}
var results = query.ToList(); // Single ToList call to materialize.
gvNominations.DataSource = results;
gvNominations.DataBind();
// Session["DataSource"] = gvNominations.DataSource; Don't persist these entities
}
Better than returning the entities would be to use .Select() to populate a view model with just the columns that you want to display. This reduces the data sent across the wire from DB to web server and from web server to client. This can also allow you to select data from referenced entities without needing to explicitly include those referenced entities nor the overhead/errors that can occur by trying to serialize entities with relations.
var results = query.Select(x => new TableListEntry
{
Id = x.Id,
Name = x.Name,
Date = x.Date,
Type = x.Type,
// ... etc.
}).ToList();
View models are also safe to persist to session state or what-have you. Don't persist Entities & avoid passing entities to the client. Entities should only exist within the scope of their DbContext. Serializing them to clients leads to issues with circular references tripping up serializers, and also means association/reassociation issues if you try to later use them within the scope of another DbContext.

Dynamic table name in Entity Framework

I am using Entity Framwork with a database-first approach. I want to change the table name or view name dynamically based on conditions.
Here, I am using V_OVT_VLD_340B_DNA_CLD or V_OVT_B_table or V_OVT_c_table to get the records.
Based upon the source, I need to call the different table name and get the records. The whole code snippet is the same, except for the table name.
Please refer below code
private dOVT_OutlierViewEntities db = new dOVT_OutlierViewEntities();
if(source == "a")
{
var result = this.db.V_OVT_VLD_340B_DNA_CLD.Where(x => x.DNA_PGM_PRTN_ID == partitionId && x.CLIENT_ID == clientId).ToList().Select(y => new ValidationModel
{
claim_validation_test_id = new List<byte?> { y.CLAIM_VLD_TEST_ID },
claim_id = y.CLAIM_ID,
Provider_ID = y.Provider_ID,
}).Take(id).ToList();
}
if(source == "b")
{
var result = this.db.v_OVT_B_table.Where(x => x.DNA_PGM_PRTN_ID == partitionId && x.CLIENT_ID == clientId).ToList().Select(y => new ValidationModel
{
claim_validation_test_id = new List<byte?> { y.CLAIM_VLD_TEST_ID },
claim_id = y.CLAIM_ID,
Provider_ID = y.Provider_ID,
}).Take(id).ToList();
}
if(source == "c")
{
var result = this.db.v_OVT_C_table.Where(x => x.DNA_PGM_PRTN_ID == partitionId && x.CLIENT_ID == clientId).ToList().Select(y => new ValidationModel
{
claim_validation_test_id = new List<byte?> { y.CLAIM_VLD_TEST_ID },
claim_id = y.CLAIM_ID,
Provider_ID = y.Provider_ID,
}).Take(id).ToList();
}
I want to modify the above implementation by dynamically attaching the table name to db context based upon condition.
string tableName = string.empty
if(source == "a")
tableName = "aTable";
if(source == "b")
tableName="bTable";
this.db.tableName.where().....
Is that possible?
You can go with a switch condition to set the table type and use that with context
switch (tableName)
{
case "a":
tableType = typeof(V_OVT_VLD_340B_DNA_CLD);
break;
case "b":
tableType = typeof(v_OVT_B_table);
break;
default:
tableType = typeof(v_OVT_C_table);
break;
}
var query = context.Set(tableType);
var result = query.Find(); //filter with this query condition
You can do something like this..
string tableName = string.empty
if(source == "a")
tableName =db.GetTable("aTable");
if(source == "b")
tableName=db.GetTable("bTable");
and then query like..
tableName.where()

LINQ's SelectMany Equivalent in OData

I have an entity called AccountAction that has property called TransactionTypes that is an ICollection of TransactionType entities.
I return a list of all the TransactionTypes that are related to all the AccountActions in a method called GetTransactionTypes. I would like to apply query options to the returned list of TransactionTypes. However, so far I have hit a wall because all of the query options are applied to AccountActions.
Is there any way I can apply query options in the URL to the returned lists of TransactionTypes? In other words, is there a way I can do a SelectMany from the URL to get the TransactionTypes related to the AccountActions to move on to apply the query options to the found TransactionTypes?
Below is an extract of the code that I am using.
[Route(FullControllerPath + "/TransactionTypes")]
public IHttpActionResult GetTransactionTypes(ODataQueryOptions<AccountAction> queryOptions, bool addCols, int? skip, int? take)
{
using (AccountActionManagement _accountActionManage = new AccountActionManagement(this.GenerateInformation()))
{
_accountActionManage.SetTraslationList("DATASTRUCT-CONFIG-ACCOUNTACTIONTRANSACTIONTYPE", language);
// Query composition
IQueryable<TransactionType> query = queryOptions.ApplyTo(_accountActionManage.GetTypeAsQueryable<AccountAction>())
.OfType<AccountAction>()
.SelectMany(aa => aa.TransactionTypes)
.Include(tt => tt.AccountActionForDefaultTransactionType.DefaultTransactionType);
var queryData = query.Select(tt => new
{
Id = tt.Id,
Name = tt.Name,
Operation = tt.Operation,
Type = tt.Type,
Default = tt.AccountActionForDefaultTransactionType != null &&
tt.AccountActionForDefaultTransactionType.DefaultTransactionType.Id == tt.Id,
Version = tt.AccountActionForDefaultTransactionType.Version
});
// Get count
int totalRows = queryData.Count();
// Get biggest version in query
var maxVersion = queryData.Max(i => i.Version);
// Get data from database
var queryResult = queryOptions.OrderBy == null
? queryData.OrderBy(i => i.Id)
.Skip(skip ?? 0)
.Take(take ?? totalRows)
.ToList()
: queryData.Skip(skip ?? 0)
.Take(take ?? totalRows)
.ToList();
...}}
As seen in the diagram below, AccountAction has a many-to-many relationship to TransactionType. AccountAction has the first role and TransactionType has the second role.
I found a workaround for this issue. I realized that I was not passing the right type to the ApplyTo method. Now, I apply the query options to an IQueryable of TransactionTypes instead of applying the query options to an IQueryable of AccountActions.
Below is the code with the described modification. Also, a diffchecker of the change I made is here.
[Route(FullControllerPath + "/TransactionTypes")]
public IHttpActionResult GetTransactionTypes(ODataQueryOptions<AccountAction> queryOptions, bool addCols, int? skip, int? take)
{
using (AccountActionManagement _accountActionManage = new AccountActionManagement(this.GenerateInformation()))
{
_accountActionManage.SetTraslationList("DATASTRUCT-CONFIG-ACCOUNTACTIONTRANSACTIONTYPE", language);
// Query composition
IQueryable<TransactionType> query = queryOptions.ApplyTo(_accountActionManage.GetTypeAsQueryable<AccountAction()
.SelectMany(aa => aa.TransactionTypes)
.Include(aa => aa.AccountActionForDefaultTransactionType.DefaultTransactionType))
.OfType<TransactionType>();
var queryData = query.Select(tt => new
{
Id = tt.Id,
Name = tt.Name,
Operation = tt.Operation,
Type = tt.Type,
Default = tt.AccountActionForDefaultTransactionType != null &&
tt.AccountActionForDefaultTransactionType.DefaultTransactionType.Id == tt.Id,
Version = tt.AccountActionForDefaultTransactionType.Version
});
// Get count
int totalRows = queryData.Count();
// Get biggest version in query
var maxVersion = queryData.Max(i => i.Version);
// Get data from database
var queryResult = queryOptions.OrderBy == null
? queryData.OrderBy(i => i.Id)
.Skip(skip ?? 0)
.Take(take ?? totalRows)
.ToList()
: queryData.Skip(skip ?? 0)
.Take(take ?? totalRows)
.ToList();
...}}

Linq to Entities orderby parent table value. (MVC)

I have a Member table which has a foreign key from ASPNETUSERS table. My ASPNETUSERS table has additional fields in it for First Name and Last Name which I would like to use as sort values in my Members Controller.
var members = from m in db.Members.Include(m => m.AspNetUser).Include(m => m.Location) select m;
This is currently the initial query i am retrieving to get me all the members and searching is easy as i can use the following syntax.
members = members.Where(m => m.AspNetUser.LastName.Contains(searchString)
|| m.AspNetUser.HomePhone.Contains(searchString)
|| m.AspNetUser.MobilePhone.Contains(searchString)
|| m.AspNetUser.FirstName.Contains(searchString)
|| m.IdentificationNumber.Contains(searchString));
However when i try to do my sorting e.g.
members = members.OrderByDescending(m => m.AspNetUser.LastName);
It "works" however it doesn't actually sort but the Last name of the related table.
Can someone advise me as to what I am doing wrong.
Thank you.
Update: Below is full Action Result Method.
public ActionResult Index(string sortOrder, string searchString, string Command)
{
//Redirect back to login page if not authenticated
if (!HttpContext.User.Identity.IsAuthenticated)
{
return RedirectToAction("Login", "Account");
}
ViewBag.LastNameSortParm = String.IsNullOrEmpty(sortOrder) ? "lastname_desc" : "";
ViewBag.FirstNameSortParm = sortOrder == "firstname" ? "firstname_desc" : "firstname";
var members = from m in db.Members.Include(m => m.AspNetUser).Include(m => m.Location) select m;
if (Command == "Search")
{
if (!String.IsNullOrEmpty(searchString))
{
members = members.Where(m => m.AspNetUser.LastName.Contains(searchString)
|| m.AspNetUser.HomePhone.Contains(searchString)
|| m.AspNetUser.MobilePhone.Contains(searchString)
|| m.AspNetUser.FirstName.Contains(searchString)
|| m.IdentificationNumber.Contains(searchString));
}
}
else if (Command == "Reset")
{
}
switch (sortOrder)
{
case "lastname_desc":
members = members.OrderByDescending(m => m.AspNetUser.LastName);
break;
case "firstname":
members = members.OrderBy(m => m.AspNetUser.FirstName);
break;
case "firstname_desc":
members = members.OrderByDescending(m => m.AspNetUser.FirstName);
break;
default:
members = members.OrderBy(m => m.AspNetUser.LastName);
break;
}
return View(members.ToList());
}

Categories

Resources