I'm a newbie both to C# and to LINQ and would appreciate a small push in the right direction.
Firstly, I have an Overrides SQL table (and a corresponding EF DB context), which has a Type, Value, and Override Value. The idea is that for a particular kind ("Type") of override, the code can check a particular value and go see if there is an override value that should be used instead.
var branchOverrides = overridesSqlContext.Overrides
.Where(q => q.Type == "Branch Override")
.Select(s => new
{
s.Value,
s.OverrideValue
});
In this case, I want the list of different override values of the "Branch Override" type. From there, I would like to be able to retrieve a specific override value at a given point within my code. How can I query the branchOverrides variable I've created to be able to say something like:
string readingOverride = select OverrideValue from branchOverrides where Value = "Reading"
My code will need to be able to read various override values for different branches at different points, and being able to query the branchOverrides variable at any point would seem like the ideal approach.
Thank you for any assistance on this.
You can use Single() on the query object you have:
string readingOverride = branchOverrides
.Single(bo => bo.Value == "Reading")
.OverrideValue;
This will throw an exception if an entry doesn't exist though so you probably want to use SingleOrDefault instead and check for a null return.
Also note that the branchOverrides object here is an IQueryable<> which means that every time you use it, it will send a query to the database. You may want to materialise that to a local list by adding .ToList() after the Select(...). Alternatively, you may want to look at caching this data, especially if it's going to be used frequently.
If I understood you right, you want the entry with Value = "Reading" and Type="Branch Override":
var branchOverride = overridesSqlContext.Overrides
.SingleOrdDefault(q => q.Type == "Branch Override"
&& q.Value == "Reading")
.Select(s => new
{
s.Value,
s.OverrideValue
});
if (branchOverride != null)
{
// do whatever ...
}
For performance issue is good to put .ToList() in the end of your LINQ expression if you need to iterante over that list too many times.
var branchOverrides = overridesSqlContext.Overrides
.Where(q => q.Type == "Branch Override")
.Select(s => new
{
s.Value,
s.OverrideValue
}).ToList();
If it you will load the entire list into the memory avoiding to execute the sql query to fetch the data if you need to iterate through your list.
Other thing that you can do is:
string readingOverride = string.Empty;
var branchOverride = branchOverrides.FirstOrDefault(x => x.Value == "Reading");
if(branchOverride != null)
{
readingOverride = branchOverride.OverrideValue;
}
Hope that helps.
If Value is unique within "Branch Override" perhaps you want to turn it to a dictionary for fast lookup
var branchOverrides = overridesSqlContext.Overrides
.Where(q => q.Type == "Branch Override")
.Select(s => new
{
s.Value,
s.OverrideValue
})
.ToDictionary(k => k.Value, v => v.OverrideValue);
Then later on you can find the override value quickly and efficiently
var readingOverride = branchOverrides["Reading"];
Related
I am trying to make a query to a database view based on earlier user-choices. The choices are stored in lists of objects.
What I want to achieve is for a record to be added to the reportViewList if the stated value exists in one of the lists, but if for example the clientList is empty the query should overlook this statement and add all clients in the selected date-range. The user-choices are stored in temporary lists of objects.
The first condition is a time-range, this works fine. I understand why my current solution does not work, but I can not seem to wrap my head around how to fix it. This example works when both a client and a product is chosen. When the lists are empty the reportViewList is obviously also empty.
I have played with the idea of adding all the records in the date-range and then removing the ones that does not fit, but this would be a bad solution and not efficient.
Any help or feedback is much appreciated.
List<ReportView> reportViews = new List<ReportView>();
using(var dbc = new localContext())
{
reportViewList = dbc.ReportViews.AsEnumerable()
.Where(x => x.OrderDateTime >= from && x.OrderDateTime <= to)
.Where(y => clientList.Any(x2 => x2.Id == y.ClientId)
.Where(z => productList.Any(x3 => x3.Id == z.ProductId)).ToList();
}
You should not call AsEnumerable() before you have added eeverything to your query. Calling AsEnumerable() here will cause your complete data to be loaded in memory and then be filtered in your application.
Without AsEnumerable() and before calling calling ToList() (Better call ToListAsync()), you are working with an IQueryable<ReportView>. You can easily compose it and just call ToList() on your final query.
Entity Framework will then examinate your IQueryable<ReportView> and generate an SQL expression out of it.
For your problem, you just need to check if the user has selected any filters and only add them to the query if they are present.
using var dbc = new localContext();
var reportViewQuery = dbc.ReportViews.AsQueryable(); // You could also write IQuryable<ReportView> reportViewQuery = dbc.ReportViews; but I prefer it this way as it is a little more save when you are refactoring.
// Assuming from and to are nullable and are null if the user has not selected them.
if (from.HasValue)
reportViewQuery = reportViewQuery.Where(r => r.OrderDateTime >= from);
if (to.HasValue)
reportViewQuery = reportViewQuery.Where(r => r.OrderDateTime <= to);
if(clientList is not null && clientList.Any())
{
var clientIds = clientList.Select(c => c.Id).ToHashSet();
reportViewQuery = reportViewQuery.Where(r => clientIds.Contains(y.ClientId));
}
if(productList is not null && productList.Any())
{
var productIds = productList.Select(p => p.Id).ToHashSet();
reportViewQuery = reportViewQuery.Where(r => productIds .Contains(r.ProductId));
}
var reportViews = await reportViewQuery.ToListAsync(); // You can also use ToList(), if you absolutely must, but I would not recommend it as it will block your current thread.
I'm having trouble understanding .Select and .Where statements. What I want to do is select a specific column with "where" criteria based on another column.
For example, what I have is this:
var engineers = db.engineers;
var managers = db.ManagersToEngineers;
List<ManagerToEngineer> matchedManager = null;
Engineer matchedEngineer = null;
if (this.User.Identity.IsAuthenticated)
{
var userEmail = this.User.Identity.Name;
matchedEngineer = engineers.Where(x => x.email == userEmail).FirstOrDefault();
matchedManager = managers.Select(x => x.ManagerId).Where(x => x.EngineerId == matchedEngineer.PersonId).ToList();
}
if (matchedEngineer != null)
{
ViewBag.EngineerId = new SelectList(new List<Engineer> { matchedEngineer }, "PersonId", "FullName");
ViewBag.ManagerId = new SelectList(matchedManager, "PersonId", "FullName");
}
What I'm trying to do above is select from a table that matches Managers to Engineers and select a list of managers based on the engineer's id. This isn't working and when I go like:
matchedManager = managers.Where(x => x.EngineerId == matchedEngineer.PersonId).ToList();
I don't get any errors but I'm not selecting the right column. In fact the moment I'm not sure what I'm selecting. Plus I get the error:
Non-static method requires a target.
if you want to to select the manager, then you need to use FirstOrDefault() as you used one line above, but if it is expected to have multiple managers returned, then you will need List<Manager>, try like:
Update:
so matchedManager is already List<T>, in the case it should be like:
matchedManager = managers.Where(x => x.EngineerId == matchedEngineer.PersonId).ToList();
when you put Select(x=>x.ManagerId) after the Where() now it will return Collection of int not Collection of that type, and as Where() is self descriptive, it filters the collection as in sql, and Select() projects the collection on the column you specify:
List<int> managerIds = managers.Where(x => x.EngineerId == matchedEngineer.PersonId)
.Select(x=>x.ManagerId).ToList();
The easiest way to remember what the methods do is to remember that this is being translated to SQL.
A .Where() method will filter the rows returned.
A .Select() method will filter the columns returned.
However, there are a few ways to do that with the way you should have your objects set up.
First, you could get the Engineer, and access its Managers:
var engineer = context.Engineers.Find(engineerId);
return engineer.Managers;
However, that will first pull the Engineer out of the database, and then go back for all of the Managers. The other way would be to go directly through the Managers.
return context.Managers.Where(manager => manager.EngineerId == engineerId).ToList();
Although, by the look of the code in your question, you may have a cross-reference table (many to many relationship) between Managers and Engineers. In that case, my second example probably wouldn't work. In that case, I would use the first example.
You want to filter data by matching person Id and then selecting manager Id, you need to do following:
matchedManager = managers.Where(x => x.EngineerId == matchedEngineer.PersonId).Select(x => x.ManagerId).ToList();
In your case, you are selecting the ManagerId first and so you have list of ints, instead of managers from which you can filter data
Update:
You also need to check matchedEngineer is not null before retrieving the associated manager. This might be cause of your error
You use "Select" lambda expression to get the field you want, you use "where" to filter results
I am using MVC 4 and entity framework, I am retrieving emails from the server:
var data = db.Candidates.Where(c => ids.Contains(c.ID) && c.Email1 != null).Select(c => new { c.Email1, c.ID }).ToList();
My first question: Does LINQ allow me to return an empty string form the Email1 field if it is null, similar to SQL coalesce? (I would remove the null test from the where clause).
2nd question: what would be the easiest object to use (to replace the "var data =" if I wanted to get c.Name along with the Email1, then use both in a loop? Should I create a model for just 2 fields?
Thanks so much in advance for any insights.
My first question: Does LINQ allow me to return an empty string form the Email1 field if it is null, similar to SQL coalesce? (I would remove the null test from the where clause).
Yes, there is the ?? operator that works similar to the coalesce.:
new { Email1 = c.Email1 ?? "", c.ID } //String.Empty would be nicer, but i think it depends on EF version if you are allowed to use it.
For your second question, if this is the only place you are going to use them, then anonymous is pretty fine.
If you want to use this on other places, yes create an object just with two properties... That's the object's purpose after all. (or maybe a struct?)
Ask one question at a time.
2a. The Null Coalescence operator in C# is ??.
2b. This may or may not be converted by your Linq Provider into a database query.
Do it like this,
var data = db.Candidates
.Where(c => ids.Contains(c.ID))
.Select(c => new
{
Id = c.Id,
Email1 = c.Email1 ?? string.Empty,
Name = c.Name
});
foreach(var row in data)
{
var name = row.Name // etc...
}
If your Linq Provider does not support the ?? operator, put in a .ToList() and use linq-to-objects to perform the tranformation like this,
var data = db.Candidates
.Where(c => ids.Contains(c.ID))
.ToList() // <-- from here is Linq-To-Objects
.Select(c => new
{
Id = c.Id,
Email1 = c.Email1 ?? string.Empty,
Name = c.Name
});
I have a database where I'm wanting to return a list of Clients.
These clients have a list of FamilyNames.
I started with this
var query = DbContext.Clients.Include(c => c.FamilyNames).ToList() //returns all clients, including their FamilyNames...Great.
But I want somebody to be able to search for a FamilyName, ifany results are returned, then show the clients to the user.
so I did this...
var query = DbContext.Clients.Include(c => c.FamilyNames.Where(fn => fn.familyName == textEnteredByUser)).ToList();
I tried...
var query = DbContext.Clients.Include(c => c.FamilyNames.Any(fn => fn.familyName == textEnteredByUser)).ToList();
and...
var query = DbContext.FamilyNames.Include(c => c.Clients).where(fn => fn.familyname == textEnteredByUser.Select(c => c.Clients)).ToList();
What I would like to know (obviously!) is how I could get this to work, but I would like it if at all possible to be done in one query to the database. Even if somebody can point me in the correct direction.
Kind regards
In Linq to Entities you can navigate on properties and they will be transformed to join statements.
This will return a list of clients.
var query = DbContext.Clients.Where(c => c.FamilyNames.Any(fn => fn == textEnteredByUser)).ToList();
If you want to include all their family names with eager loading, this should work:
var query = DbContext.Clients.Where(c => c.FamilyNames.Any(fn => fn == textEnteredByUser)).Include(c => c.FamilyNames).ToList();
Here is some reference about loading related entities if something doesn't work as expected.
You can use 'Projection', basically you select just the fields you want from any level into a new object, possibly anonymous.
var query = DbContext.Clients
.Where(c => c.FamilyNames.Any(fn => fn == textEnteredByUser))
// only calls that can be converted to SQL safely here
.Select(c => new {
ClientName = c.Name,
FamilyNames = c.FamilyNames
})
// force the query to be materialized so we can safely do other transforms
.ToList()
// convert the anon class to what we need
.Select(anon => new ClientViewModel() {
ClientName = anon.ClientName,
// convert IEnumerable<string> to List<string>
FamilyNames = anon.FamilyNames.ToList()
});
That creates an anonymous class with just those two properties, then forces the query to run, then performs a 2nd projection into a ViewModel class.
Usually I would be selecting into a ViewModel for passing to the UI, limiting it to just the bare minimum number of fields that the UI needs. Your needs may vary.
I'm writing an ASP.NET Web Pages application and in it, I have a massive LINQ to Entities query. This query pulls data from a table in the database, filters it, groups the data twice, and adds extra properties to the result set. I then loop through the table, outputting the rows.
The query is quite big, sorry:
accountOrders = db.EventOrders
.Where(order => order.EventID == eventID)
.OrderBy(order => order.ProductCode)
.GroupBy(order => new { order.AccountNum, order.Exhibitor, order.Booth })
.Select(orders =>
new {
Key = orders.Key,
ProductOrders = orders
.GroupBy(order => new { order.ProductCode, order.Product, order.Price })
.Select(productOrders =>
new {
Key = productOrders.Key,
Quantity = productOrders.Sum(item => item.Quantity),
HtmlID = String.Join(",", productOrders.Select(o => (o.OrderNum + "-" + o.OrderLine))),
AssignedLines = productOrders.SelectMany(order => order.LineAssignments)
})
})
.Select(account =>
new {
Key = account.Key,
// Property to see whether a booth number should be displayed
HasBooth = !String.IsNullOrWhiteSpace(account.Key.Booth),
HasAssignedDigitalLines = account.ProductOrders.Any(order => order.AssignedLines.Any(line => line.Type == "digital")),
// Dividing the orders into their respective product group
PhoneOrders = account.ProductOrders.Where(prod => ProductCodes.PHONE_CODES.Contains(prod.Key.ProductCode)),
InternetOrders = account.ProductOrders.Where(prod => ProductCodes.INTERNET_CODES.Contains(prod.Key.ProductCode)),
AdditionalOrders = account.ProductOrders.Where(prod => ProductCodes.ADDITIONAL_CODES.Contains(prod.Key.ProductCode))
})
.ToList();
I use the added properties to help style the output. For example, I use HasBooth property to check whether or not I should output the booth location in brackets beside the exhibitor name. The problem is I have to save this big query as an IEnumerable, meaning I get the error: Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type. Should I even be manipulating the query this way?
Any advice is much appreciated!
At some point, you are passing in a dynamic datatype to the method, which in turn changes the return type to simply dynamic. You can either cast the dynamic type to a type that is recognised at compile time or explicitly set the return type instead of using var.
You can read more about this issue here: http://www.mikesdotnetting.com/Article/198/Cannot-use-a-lambda-expression-as-an-argument-to-a-dynamically-dispatched-operation