How to find the max-ID by using a lambda-query? - c#

I wanna find the next ID (ID_Titel) by selecting ID_Artiest. I try it by using LINQ (lambda-query). What do I do wrong.
By example:
For ID_Artiest 2, I wanna have ID_Titel n+1.
First I try it without .DefaultIfEmpty(). and then I get a message: Sequence Contains No Element
Second I try it with .DefaultIfEmpty().: And then I get the message: Object reference not set to an instance of an object.
This is the source:
private void UpdateTitel(NoteringDataType itemDezeWeek)
...
...
TitelDataType titel = new TitelDataType();
if (Titelslijst.Count > 0)
titel.ID_Titel = Titelslijst.Where(t => t.ID_Artiest == itemDezeWeek.ID_Artiest).OrderBy(t => t.ID_Titel).DefaultIfEmpty().Max(t => t.ID_Titel) + 1;
else
titel.ID_Titel = 1;

Both of the errors you got are expected if the first Where doesn't find any matching values.
Please try this:
titel.ID_Titel = Titelslijst.Where(t => t.ID_Artiest == itemDezeWeek.ID_Artiest)
.Select(t => t.ID_Titel)
.OrderByDescending(t => t)
.FirstOrDefault() + 1;
Since the Select transforms the collection to an enumeration of ints (I am assuming), the .FirstOrDefault() should return 0 if the Where() didn't return any matches. In your code, .DefaultIfEmpty() would have returned an IEnumerable<T> containing null because it was called on an enumeration of whatever Titelslijst contains.

Apparently the errors you see happen when nothing in the list matches your where condition. DefaultIfEmpty in this case will give you just a single list with null, which does not help a lot.
To avoid such situations you should really assign ID to 1 if there no entries found. So the logic should be like:
TitelDataType titel = new TitelDataType();
int id = 1;
if (Titelslijst.Count > 0)
{
var titles = Titelslijst.Where(t => t.ID_Artiest == itemDezeWeek.ID_Artiest);
if (titles.Any())
id = titles.Max(t => t.ID_Titel) + 1;
}
titel.ID_Titel = id;

More short, more right:
Titelslijst.Where(t => t.ID_Artiest == itemDezeWeek.ID_Artiest).Select(t => t.ID_Artiest).Max();

Related

Linq to find inside Array of objects

I have the below code . Here i want to find out servicelevelid in lstServiceLevels List which is an array of objects of ServiceLevelDetails where ServiceLevelName is "Basic"
Could anyone please help me to get it ?
public class ServiceLevelDetails
{
public int servicelevelid;
public string ServiceLevelName;
}
class Program
{
static void Main(string[] args)
{
IList<ServiceLevelDetails> lstServiceLevels = new List<ServiceLevelDetails>();
ServiceLevelDetails one = new ServiceLevelDetails();
one.servicelevelid=1;
one.ServiceLevelName="Basic";
ServiceLevelDetails Two = new ServiceLevelDetails();
Two.servicelevelid = 2;
Two.ServiceLevelName = "Enhanced";
lstServiceLevels.Add(one);
lstServiceLevels.Add(Two);
var test = from LevelName in lstServiceLevels
let LevelName= obj as ServiceLevelDetails
where LevelName.ServiceLevelName == "Basic"
select LevelName;
//getting error in the above code .
}
}
There is nothing in your scope called obj, so it's not clear why this is in your query.
In LINQ query syntax, it sounds like you want this:
from serviceLevel in lstServiceLevels
where serviceLevel.ServiceLevelName == "Basic"
select serviceLevel;
Or in LINQ method syntax:
lstServiceLevels.Where(x => x.ServiceLevelName == "Basic");
If, as you suggest in the comments, you want the id for a specific name:
var id = lstServiceLevels
.Where(x => x.ServiceLevelName == "Basic")
.Select(x => x.servicelevelid)
.Single();
here is an alternative with which you don't run into exceptions
when the element cannot be found.
look for the first occurrence of this element:
// FirstOrDefault will return null if nothing was found
// but it also will return only the first element!
var id_test = lstServiceLevels.FirstOrDefault(x => x.ServiceLevelName == "Basic");
// then you can check for it and take the ID if it was found
// or else assign some other value
int id = id_test != null ? id_test.servicelevelid : 0;
it's of course a matter of taste either to use try/catch or null testing :)

List<string> get item that starts with Name= using LINQ

How can i retrieve from a List<string> the item(value) that starts with Name=
If the list contains the values:
Name=Jhon Smith
Age=20
Location=CityName
I want to get the value Jhon Smith.
I do know how to traditionally loop through the list using foreach and have a condition if value starts with Name= ... but I'm not that good with LINQ.
This will throw an exception if there is no such element in the collection.
You can use FirstOrDefault and check if you get null or an element to check
whether there was a match or not.
list.First(x => x.StartsWith("Name=")).Substring(5)
This won't throw an exception:
var prefix = "Name=";
var elem = list.FirstOrDefault(item => item.StartsWith(prefix));
if (elem != null) {
return elem.Substring(prefix.Length)
} else {
return null;
}
Use Single or SingleOrDefault like this:
var result = list.Single(s => s.StartsWith("Name=")).Substring(5);
or
string result = string.Empty;
var element = list.SingleOrDefault(s => s.StartsWith("Name="));
if (element == null)
{
//"Name=" not present, throw an exception
}
result = element.Substring(5);
or similarly with First, or FirstOrDefault depending on what you exactly want.
There was another interesting answer by the user canon, but was deleted (I don't know why):
var result = list
.Where(x => x.StartsWith("Name="))
.Select(x => x.Substring(5))
.FirstOrDefault();
Its advantage is that it won't throw regardless of the input data.
String match = list.FirstOrDefault( str => str.IndexOf("Name=", StringComparison.InvariantCulture) > -1 );
return match.Substring("Name=".Length);

How to load just the last record from entity with LINQ?

I want to fetch value of field named "Gram" from the last record and put its value into a variable, without using any conditions.
First I tried
int value = int.Parse(Entity.TblGold.LastOrDefault().Gram.ToString());
Second I tried
int value = int.Parse(Entity.TblGold.Select(p => p.Gram).Last().ToString());
I just receive this exception:
LINQ to Entities does not recognize the method 'DataModel.TblGold LastOrDefault[TblGold](System.Linq.IQueryable``1[DataModel.TblGold])' method, and this method cannot be translated into a store expression.
Last or LastOrDefault are not supported in LINQ to Entities. You can either iterate your query using ToList or ToArray and then apply Last or you can order by descending and then use the First like:
int value = int.Parse(Entity.TblGold
.OrderByDescending(p => p.Gram)
.Select(r => r.Gram)
.First().ToString());
You can't do it in one query, but you can do it in two.
var countOfRows = tbl.Count();
var lastRow = tbl.Skip(countOfRows - 1).FirstOrDefault();
If you have an incremental id:
int idx = tb.Max(i => i.Id);
var row = tb.FirstOrDefault(i => i.Id == idx);
It is your answer and you don't need to convert.
int value = Entity.TblGold.OrderByDescending(p => p.Id).First().Gram;
You can use order by 1 == 1 and it works
var countOfRows = tbl.Count();
var lastRow = tbl.OrderBy(c => 1 == 1).Skip(countOfRows - 1).FirstOrDefault();

More fancy way to deal with selecting non-existing records to avoid null exception errors

It was bugging me, why neither of ?? or == null works when I want to select a record that doesn't exist. For example if I do
var foo = db.Where(k => k.ID == 9) ?? bar;
it works fine, but it crashes (Object reference not set to an instance of an object error) when I want to select single field:
var foo = db.Where(k => k.ID == 9).Select(k => k.Field).FirstOrDefault() ?? bar;
//or
var foo = bar;
if (db.Where(k => k.ID == 9) != null)
foo = db.Where(k => k.ID == 9).Select(k => k.Field).FirstOrDefault()
In both cases It crashes, which seems to be pretty logical.
Normally I wouldn't mind doing
if (Order.A_Data.Where(k => k.FieldID == 9).Count() > 0)
writeText(cb, Order.A_Data.Where(k => k.FieldID == 9).
Select(k => k.Content).
FirstOrDefault().ToString(),
left_margin, top_margin - 24, f_cn, 10);
//this looks more like the actual code, but in fact it has multiple 'where' conditions and more tables connected with an external key, that's why I don't want to write the same thing over and over again
//or creating new variables
but sadly I have to re-create really complicated PDF document with literally hundreds of fields* like these obtaining data from dozens of tables, so every "trickier way" will be very helpfull. Also, it's very slow.
Or maybe there is a keep-your-null-exception-errors-for-yourself-and-just-return-an-empty-string switch?
*278 to be more specific. So I can either find easy way to do this, or prepare another bucket of coffee, sigh and do my job
Can't you just use:
var foo = db.Where(k => k.ID == 9).FirstOrDefault().Field;
That should return a default object with null properties if it didn't find a row.
Edit Now I understand. You want something like this:
var foo = db.Where(k => k.ID == 9).DefaultIfEmpty(bar).First().Field;
In response to your edit, a database nvarchar usually maps to a String, a reference type.
But when a database nvarchar is null, it returns DBNull.Value to the .NET client instead of null. (Must be the most confusing thing in all of .NET.)
Try:
var foo = bar;
var row = db.FirstOrDefault(k => k.ID == 9);
if (row != null && row.Field != DBNull.Value)
foo = row.Field;
The problem seems to be here
if (Order.A_Data.Where(k => k.FieldID == 9).Count() > 0)
writeText(cb, Order.A_Data.Where(k => k.FieldID == 9).
Select(k => k.Content).
FirstOrDefault().ToString(),
left_margin, top_margin - 24, f_cn, 10);
You select where ID=9, take only the Content and then FirstOrDefault. So far so good, if there is a match you get the order. But if there is no match the FirstOrDefault will return null and therefore the ToString will throw an exception. Try this:
var order = Order.A_Data.SingleOrDefault(k => k.FieldID == 9); // Get the order
if(order != null){ // and check if we have a match
writeText(cb, order.Content.ToString(), // write the Content of the match
left_margin, top_margin - 24, f_cn, 10);
}else{
// handle however you want when there are no orders matching
}
Note that I'm using SingleOrDefault instead of FirstOrDefault. This is because I'm assuming you will only have one order with FieldID equal to 9. If you can have more, use FirstOrDefault instead.

Index was out of range exception in query

each Route contains Locations in specific order.
For example:
NY -> LA is different from LA -> NY.
I would like to write a method that gets locations array and return true or false whether route with the same locations and order exists.
I need to do it using linq to entities and entity framework (Route and Location are entities).
Here is what I wrote:
public bool IsRouteExists(IList<LocationInRoute> locationsInRoute)
{
Route route = null;
if (locationsInRoute.Count > 0)
{
var query = GetRoutesQuery().
Where(x => x.Locations.Count() == locationsInRoute.Count);
for (int i = 0; i < locationsInRoute.Count; i++)
{
long locationId = locationsInRoute[i].LocationId;
query = query.Where(x =>
x.Locations.ElementAt(i).LocationId == locationId); //THROWS EXCEPTION
}
route = query.SingleOrDefault();
}
return route!=null;
}
I get the following exception in the marked line:
Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
What is the reason for this exception?
EDIT
The exception accurs when executing route = query.SingleOrDefault(); and the exception complains about Where(x => x.Locations.ElementAt(i).LocationId == locationId);.
I believe this query is completely wrong. First of all it is not linq-to-entities query and it will never be because linq to entities is not able to work with indexes. I think comparing ordered sequences will have to be executed in memory = linq-to-objects.
Another problem is this:
for (int i = 0; i < locationsInRoute.Count; i++)
{
long locationId = locationsInRoute[i].LocationId;
query = query.Where(x => x.Locations.ElementAt(i).LocationId == locationId);
}
route = query.SingleOrDefault();
I think this is known gotcha when using Linq, query built in loop and deferred execution - I believe this always compares locationId with the last element.
In my opinion the most efficient way to do this is stored procedure with table valued parameter passing your expected sequence and using SQL cursor to compare sequences in the stored procedure.
Looks like perhaps your x.Locations.Count() might be less than your locationsInRoute.Count. are you sure it's not? I'm saying that b/c you're calling x.Locations.ElementAt(i), which will throw if i > Count().
As a sidenote, a better solution to what you're doing is to override equality or implement an IComparer on your class that you want unique and then you can use things like Any() and Contains() for your test.
If you getting an index out of range exception it must mean that number of elements in the locationsRoute collection exceeds the number of elements in IQueryable. If you attempting to test that each location in the provided list is contained in route you should be able to do something like:
var locationIds = locationsInRoute.Select(l => l.LocationId);
query = query.Where(r => r.Locations.All(l => locationIds.Contains(l.LocationId)))
I'm guessing it has to do with your use of ElementAt, which can't be translated to SQL (see the Operators with No Translation section), to operate on your IQueryable. This materializes the IQueryable's result set on the first iteration and so subsequent iterations Route items will be unable to access their related Locations sets. This should probably only happen on the second iteration, but the myriad implications of the deferred execution nature of LINQ is not entirely clear to me in ever case ;-) HTH
You could put a breakpoint at SingleOrDefault and inspect the SQL statement it is executing there, to see why there is no record returned for the SingleOrDefault to find. Though the SQL may be pretty ugly depending on how many routes you have.
Thanks to #Ladislav Mrnka's advice, here is the solution:
public class LocationSequenceEqual : IEqualityComparer<Location>
{
public bool Equals(Location x, Location y)
{
return x.Id == y.Id;
}
public int GetHashCode(Location obj)
{
return obj.Id.GetHashCode();
}
}
public bool IsRouteExists(IList<LocationInRoute> locationsInRoute)
{
Route route = null;
if (locationsInRoute.Count > 0)
{
var query = GetRoutesQuery().
Where(x => x.Locations.Count() == locationsInRoute.Count);
query = query.Where(x => x.Locations.OrderBy(l => l.Order).
Select(l => l.Location).SequenceEqual(locations, new LocationSequenceEqual()));
route = query.FirstOrDefault();
}
return route!=null;
}
If Location has Order as you indicate above, this can be done entirely in (Linq to) SQL:
public bool IsRouteExists(IList<LocationInRoute> locationsInRoute)
{
Route route = null;
if (locationsInRoute.Count == 0)
return;
var possibleRoutes = GetRoutesQuery().
Where(x => x.Locations.Count() == locationsInRoute.Count);
var db = GetDataContext(); //get a ref to the DataContext or pass it in to this function
for (var i = 0; i < locationsInRoute.Length; i++)
{
var lcoationInRoute = locationsInRoute[i];
possibleRoutes = possibleRoutes.Where(x => x.Locations.Any(l => l.Id == locationInRoute.Id && l.Order == locationInRoute.Order));
}
route = possibleRoutes.FirstOrDefault();
return route!=null;
}

Categories

Resources