I have an odd behavior using an IEnumerable<string> with a ternary operator and a Select statement.
I have two lists with different objects. One list contains Enums the other list contains objects. Those objects do have a String property.
If one list is null or empty I want to get the values of the other list.
Here is some code:
public class ExportItem
{
public string Type;
...
}
public enum ExportType
{
ExportType1,
ExportType2,
...
}
The List<ExportItem> is always filled by a config file. The List<ExportType> is filled if command line arguments are provided. So if List<ExportType> is filled I want to use them, otherwise I want to use those from the config file.
So my code ist like this:
IEnumerable<string> exportTypes = MyListOfExportTypes != null &&
MyListOfExportTypes.Any() ? MyListOfExportTypes.Select(x => x.ToString()) :
MyListOfExportItems.Select(x => x.Type);
The thing is that exportTypes is null but I don't get it...
When I do this with if-else everything works as expected. Also if exportTypes is of type List<string> and I call ToList() after the Select statement everything works fine.
Using var a = MyListOfExportTypes.Select(x => x.ToString()); and var b = MyListOfExportItems.Select(x => x.Type); does work as expected.
Must be something with the ternary operator and/or IEnumerable. But what?
Or what do I miss? Any suggestions?
EDIT:
I now have a screenshot...
Note that the code above foreach works nevertheless...
Not sure if this was answered,
But I think that this is related to the fact that you are using LINQ deferred execution.
When writing LINQ queries,
there is a difference between creating the query and executing it.
Writing the select statement, is creating the query, adding ToList() executes it.
Think of it like writing SQL query in SQL server console (that's the writing stage),
and once you hit F5 (Or the play button) you execute it.
I hope this little code sample will help to clarify it.
public class SomeClass
{
public int X { get; set; }
public int Y { get; set; }
public void Test()
{
//Here I'm creating a List of Some class
var someClassItems = new List<SomeClass> {
new SomeClass { X = 1, Y = 1 },
new SomeClass { X = 2, Y = 2 }
};
//Here I'm creating a Query
//BUT, I'm not executing it, so the query variable, is represented by the IEnumerable object
//and refers to an in memory query
var query = someClassItems.
Select(o => o.X);
//Only once the below code is reached, the query is executed.
//Put a breakpoint in the query, click the mouse cursor inside the select parenthesis and hit F9
//You'll see the breakpoint is hit after this line.
var result = query.
ToList();
}
}
Related
I have this error
Unable to create a constant value of type 'Controllers.Administrator'. Only primitive types or enumeration types are supported in this context.
I have a variable List<Administrator> result that is getting it's values from Database.SqlQuery<Administrator>.
Administrator class looks like this
public class Administrator
{
public string AdminName { get; set; }
public string Numer { get; set; }
}
Than I have something like this
var sheet = from k in Context.Admins.AsNoTracking()
select new Admin()
{
// some other variables
Numer = k.Numer,
AdminName = result.FirstOrDefault(a => a.Numer == k.Numer).AdminName ?? String.Empty
};
AdminName line is giving me the error, why can't it work like this?
I'm trying to get a value from result into sheet that matches by the Numer.
Your problem is that the whole query is being sent to SQL Server, and when it gets to the bit where you try to create an Admin object, SQL Server goes "Huh? What's one of those?"
If you enumerate your query before that point, then the results are sent back from SQL Server to your code, where the Admin object can be created...
var sheet = Context.Admins.AsNoTracking()
.ToList()
.Select(a => new Admin()
{
// some other variables
Numer = k.Numer,
AdminName = result.FirstOrDefault(a => a.Numer == k.Numer).AdminName ?? String.Empty
});
I've converted your code to use method syntax, as I think it's easier to read for this sort of thing.
Either way, the point is to understand what bit of code gets executed where. Anything to do with your models must be executed in your code, not in the database, as that doesn't know about your models. The call to ToList() enumerates the query (ie actually runs it against the database). Everything after that then happens in your code.
As a side point, you should really be using async code for this sort of thing...
var sheet = (await Context.Admins.AsNoTracking()
.ToListAsync())
//... other code as before
I'm using LINQ to Entities to get some data from a database. Below is my query.
var location = from l in dbContext.Locations
join e in dbContext.Equipment on l.ID equals e.LocationID into rs1
from e in rs1.DefaultIfEmpty()
where ids.Contains(l.ID)
select new
{
EquipmentClass = e,
LocationID = l.ID,
LocationName = l.Name,
EquipmentName = e == null ? null : e.Name,
Description = e == null ? null : e.Description,
InServiceStatus = e == null ? false : e.InServiceStatus,
EquipmentType = e.EquipmentType.Name
};
foreach (var item in location)
{
// some logic
}
In the code above, ids is a list of ints that I pass in to filter the results. When I get the results, I see that one of the return records has a null EquipmentClass. I had performed some null checking but realized that I forgot to do null checking on one of the properties. Now I would expect to get a null reference exception on EquipmentType = e.EquipmentType.Name but I don't. To my surprise, it works just fine, and is set to null. My EquipmentClass has a property type of EquipmentType, which is another class. EquipmentType has a Name property which is a String.
Why doesn't this throw a null reference exception?
Just as a test, I removed the null check from InServiceStatus = e == null ? false : e.InServiceStatus and it fails with an invalid operation exception upon running a foreach loop using the query.
Does this mean that I only need to do null checking on non-nullable values?
Update:
foreach (var item in location)
{
var p = item.EquipmentClass.EquipmentType.Name;
}
Added this right after the query. On the assignment of p, I get a null reference exception. I'm not sure how it even gets that far, as it should fail on the first line of the foreach loop. Without the line declaring variable p, I do not get a null reference exception. If anyone can explain what is happening I would be grateful. Just for reference, the values of item.EquipmentClass and item.EquipmentType are both null by the time the foreach loop starts.
Update2:
I found this link where it seems that someone has an almost identical issue using LINQ to SQL. I get the gist of the answer but don't fully understand its potential impact on my two questions above.
If you write a LINQ query against IQueryable, what happens is that the methods that you are calling behind the scenes (such as Select, Where, etc.) don't do anything more than just recording how you have called them, i.e. they record the predicate expressions and carry over a LINQ provider. As soon as you start iterating the query, the provider is asked to execute the query model. So basically, the provider uses the expression model to give you a result of the expected type.
The provider is by no means required to actually compile or even execute the code (model) you delivered as an expression. In fact, the whole point of LINQ to SQL or LINQ to Entities is that the provider does not do this and translate the code expression to SQL instead.
Therefore, your query is actually rendered as an SQL query and the result of that query is translated back. Therefore, the variable e that you see in the query is not necessarily really created but only used for the LINQ provider to compile an SQL query. However, most database servers have null propagations.
Run the same query against LINQ to Objects and you will get your missing NullReferenceException.
Your update helped me understand your real concern. The concept you need to know in regards to LINQ queries is deferred execution in LINQ.
Please go through below links for more details:
What are the benefits of a Deferred Execution in LINQ?
Linq - What is the quickest way to find out deferred execution or not?
Now what happens in your case? You've stored your query in location variable. That particular step is just the initialization part. It doesn't really executes the query on your database through your ORM layer. This is how you can test this.
Put a break point on the line of code where you're initializing location variable with LINQ query. When debugger stops in Visual Studio, then go to SQL Server Management Studio (SSMS) and start a SQL Server Profiler session.
Now press F10 in Visual Studio to step over the code statement. At this point of time you'll see absolutely no query execution in profiler session as shown below:
That is all because LINQ query didn't get executed at all till this point of time.
Now you reach to your below line of code:
foreach (var item in location)
{
var p = item.EquipmentClass.EquipmentType.Name;
}
The moment you enter inside foreach loop, the LINQ query gets fired and you'll see the corresponding log in trace session in SQL Server profiler. So a LINQ query doesn't get fired unless it is being enumerated. This is called deferred execution i.e. the runtime defers the execution until enumeration. If you never enumerate location variable in your code then query execution will never happen at all.
So to answer your query, exception will come only when query gets fired. Not before that!
Update 1: You're saying that - I do not get a null reference exception. Yes! you will not get null reference exception until you reach to the record whose corresponding RHS joined record is missing. Have a look at below code for better understanding:
class Program
{
static void Main(string[] args)
{
var mylist1 = new List<MyClass1>();
mylist1.Add(new MyClass1 { id = 1, Name1 = "1" });
mylist1.Add(new MyClass1 { id = 2, Name1 = "2" });
var mylist2 = new List<MyClass2>();
mylist2.Add(new MyClass2 { id = 1, Name2 = "1" });
var location = from l in mylist1
join e in mylist2 on l.id equals e.id into rs1
from e in rs1.DefaultIfEmpty()
//where ids.Contains(l.ID)
select new
{
EquipmentClass = e,
InServiceStatus = e == null ? 1 : e.id,
EquipmentType = e.id
};
foreach (var item in location)
{
}
}
}
class MyClass1
{
public int id { get; set; }
public string Name1 { get; set; }
}
class MyClass2
{
public int id { get; set; }
public string Name2 { get; set; }
}
So, Now when I start iterating the location variable, it doesn't breaks in first iteration. It breaks in second iteration. It breaks when it fails to obtain the record/object corresponding to MyClass1 object having id 2 present in mylist1. mylist2 doesn't have any object with id 2. Hope this helps!
your e.EquipmentType.Name is null and it is getting assigned to EquipmentType and it is perfectly fine to assign null to a nullable type and you are using DefaultIfEmpty() which will initialize the elements with their default value if it fails to match any condition and in this case your e.ElementType.Name is getting set to null which is fine I guess. use ToList() that will throw the exception.
I hope I am making some sense and you people might have discussed this.
I have a class to handle some data :
public class User
{
public string Name;
public string Date;
}
In another class,i create a List of User class and add data as follows :
public class Display
{
List<User> userData = new List<User>;
private void add()
{
User udata = new User;
udate.Name = "Abc";
udata.Date = "1/1/18";
userData.Add(udata);
}
}
My question is, after adding some data,how do i update it ? Say i have added a data(udata is what i mean) with a Name of ABC,how do i update it?
Since your list contains a mutable type, all you need to do is get a reference to the specific item you want to update.
That can be done in a number of ways - using it's index, using the Find method, or using linq are the first three that comes to mind.
Using index:
userData[0]?.Name = "CBA";
Using Find:
userData.Find(u => u.Name = "Abc")?.Name = "CBA";
Using linq (FirstOrDefault is not the only option):
userData.FirstOrDefault(u => u.Name = "Abc")?.Name = "CBA";
Note the use of null conditional operator (]? and .?) it prevents a null reference exception in case the item is not found.
Update
As Ak77th7 commented (thanks for that!), the code in this answer wasn't tested and will cause a compilation error -
error CS0131: The left-hand side of an assignment must be a variable,
property or indexer
The reason for this is the null-conditional operator (?.).
You can use it to get values from properties, but not for setting them.
The fix is either to accept the fact that your code might throw a NullReferenceException (which I personally believe has no room in production-grade code) or to make your code a bit more cumbersome:
// Note: Possible null here!
userData.Find(u => u.Name.EndsWith("1")).Name = "Updated by Find";
// Safe, but cumbersome
var x = userData.FirstOrDefault(u => u.Name.EndsWith("2"));
if(x is not null)
{
x.Name = "Updated by FirstOrDefault";
}
See a live demo on SharpLab.IO
Nothing tricky, really (but does use System.Linq)
**EDIT: Changed Single to First to avoid error if there are two users with the same name. **
void Update(string name, string newName)
{
var user = userData.First(u => u.Name == name);
user.Name = newName;
}
Notice this changes the object, and the List maintains reference to the changed object.
I have some complex business definitions that I'd like to define once and reuse them in Linq to Entities and also use them in other expressions that build upon it.
My attempt is below, and this worked when I was originally passing the ConvertOrderDetailsToViewModel a List, but I want to perform this on an IQueryable, which results in the error:
LINQ to Entities does not recognize the method 'System.Decimal Invoke(OrderDetail)' method, and this method cannot be translated into a store expression.
Is there a way to achieve this natively (without 3rd party libraries)?
Looks like from this answer, you can define these functions in the database itself, and then call them from C#, but again, looking to do this solely in C# code if possible.
Also came across this answer which looks like it works for a Where expression, but when I try to implement it for my select expression, I get the this error, which I get, because I am trying to assign the expression to a decimal.
Cannot implicitly convert type Expression<Func<OrderDetail, decimal>> to 'decimal'
Here's the expression function:
public static Expression<Func<OrderDetail, decimal>> calcQtyNeedOD = (od) => (od.OrderedQuantity - od.PickedQuantity);
View model creation method that calls this function:
public List<AllocationNeedViewModel> ConvertOrderDetailsToViewModel(IQueryable<OrderDetail> qryShipOrderDtls, List<int> itemIdsToExclude, bool boolGroupViewModelByItemAndRemnant)
{
var qryAllocNeedVMTEST =
from od in qryShipOrderDtls
select new AllocationNeedViewModel()
{
WorkReleaseHeaderId = od.OrderHeader.WorkReleaseHeaderId.Value,
OrderHeaderId = od.OrderHeaderId,
OrderDetailId = od.Id,
ItemId = od.ItemId.Value,
ItemDescription = od.Item.Description,
IsInventoryTracked = od.Item.TrackInventory,
QtyNeed = calcQtyNeedOD(od),
QtyRemain = 0,
QtyAllocated = 0,
IsAllocated = false
}
;
return qryAllocNeedVMTEST.ToList();
}
To Make this more complex, there are other properties that I also want to have a reusable expression for, which would also use this first expression...i.e.
public static readonly Expression<Func<OrderDetail, decimal>> calcQtyRemainOD =
(od) => calcQtyNeedOD.Compile().Invoke(od) - calcQtyAllocatedOD.Compile().Invoke(od);
UPDATE #1
SEE UPDATE #2...this solution does NOT work!
Though so far no one has been able to provide a native way to reuse select expressions across queries, I did find a partial solution that works in being able to reuse them within the same query. Once you project/assign an expression to an entity's property, you can (unlike in T-SQL) then specify that property as part of another subsequent property expression.
Example - this shows the full expressions for each property projection. In this example, QtyRemain is essentially just QtyNeed - QtyAllocated. I was re-specifying those again in the QtyRemain assignment:
public List<AllocationNeedViewModel> ConvertOrderDetailsToViewModel(IQueryable<OrderDetail> qryShipOrderDtls, List<int> itemIdsToExclude, bool boolGroupViewModelByItemAndRemnant)
{
var qryAllocNeedVM = qryShipOrderDtls
.Select(od => new AllocationNeedViewModel() //Get all Work Order Detail Needs for Work Release
{
QtyNeed = (od.OrderedQuantity - od.PickedQuantity),
QtyAllocated = (od.AllocatedInventories.Count == 0 ? 0 : od.AllocatedInventories.Where(ai => ai.StatusId < _AllocatedInventoryProcessedStatus).Sum(ai => ai.AllocatedQty)),
QtyRemain = (od.OrderedQuantity - od.PickedQuantity) - (od.AllocatedInventories.Count == 0 ? 0 : od.AllocatedInventories.Where(ai => ai.StatusId < _AllocatedInventoryProcessedStatus).Sum(ai => ai.AllocatedQty))
}
);
return qryAllocNeedVM.ToList();
}
Instead, you can simply use the already defined properties in that QtyRemain property assignment, like so:
public List<AllocationNeedViewModel> ConvertOrderDetailsToViewModel(IQueryable<OrderDetail> qryShipOrderDtls, List<int> itemIdsToExclude, bool boolGroupViewModelByItemAndRemnant)
{
var qryAllocNeedVM = qryShipOrderDtls
.Select(od => new AllocationNeedViewModel() //Get all Work Order Detail Needs for Work Release
{
QtyNeed = (od.OrderedQuantity - od.PickedQuantity),
QtyAllocated = (od.AllocatedInventories.Count == 0 ? 0 : od.AllocatedInventories.Where(ai => ai.StatusId < _AllocatedInventoryProcessedStatus).Sum(ai => ai.AllocatedQty)),
QtyRemain = this.QtyNeed - this.QtyAllocated
}
);
return qryAllocNeedVM.ToList();
}
Though this is not a full solution to my original question, it is a partial solution that gets you some of the benefits desired.
UPDATE #2
I was wrong on UPDATE #1. Though this works and compiles and seems to generate SQL, it is not correct. The returned value of this.QtyNeed when being used in subsequent expressions always results to 0. :(
Have you considered the following?:
public static Func<OrderDetail, decimal> calcQtyNeedOD = (od) => (od.OrderedQuantity - od.PickedQuantity);
public List<AllocationNeedViewModel> ConvertOrderDetailsToViewModel(IQueryable<OrderDetail> qryShipOrderDtls, List<int> itemIdsToExclude, bool boolGroupViewModelByItemAndRemnant)
{
return qryShipOrderDtls
.ToArray()
.Select(new AllocationNeedViewModel
{
WorkReleaseHeaderId = od.OrderHeader.WorkReleaseHeaderId.Value,
OrderHeaderId = od.OrderHeaderId,
OrderDetailId = od.Id,
ItemId = od.ItemId.Value,
ItemDescription = od.Item.Description,
IsInventoryTracked = od.Item.TrackInventory,
QtyNeed = calcQtyNeedOD(od),
QtyRemain = 0,
QtyAllocated = 0,
IsAllocated = false
}).ToList();
}
The original query was trying to execute the Func against the actual database (which won't understand your expression). Since you are not filtering your query (no where clause), return the entire set so that you can project using your local Func.
i asked this a few weeks ago, but couldnt get any of the suggested answers working, so i would be grateful for any help on this:
i have a list of event Ids returned from an xml document as shown below
public IEnumerable<EventFeed> GetEventIdsByEventDate(DateTime eventDate)
{
return (from feed in xmlDoc.Descendants("Show")
from ev in feed.Elements("Event")
where Convert.ToDateTime(ev.Attribute("Date").Value).ToShortDateString() == eventDate.ToShortDateString()
select new EventFeed()
{
EventShowCode = feed.Attribute("Code").Value
}).ToList();
}
i now need to query my database to match events that equal the eventIds returned from the above method. so i would have something like:
select * from eventsdb where eventId in GetEventIdsByEventDate()
how can i do this using LINQ
thanks
kb
Hi Prutswonder, ive created the method below based on your suggestion
public IEnumerable<EventFeed> foo(DateTime str)
{
var foo = from f in GetAllEventsFromDatabase().ToList()
where GetAllEventsByDate(str).Contains(f.EventShowCode)
select e;
return (IEnumerable<EventFeed>) foo;
}
but on compile i get the following error
Error 7 The type arguments for method 'System.Linq.Enumerable.Contains<TSource>(System.Collections.Generic.IEnumerable<TSource>, TSource)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
GetAllEventsFromDatabase:
public IEnumerable<EventFeed> GetAllEventsFromDatabase()
{
var allEvents = from eventsList in GetEventsList()
select new EventFeed()
{
EventName = eventsList.Title,
EventSummary = eventsList.Introduction,
EventShowCode = eventsList.EventId,
EventImageSmall = eventsList.EventImageThumbUrl,
EventUrl = eventsList.Url,
EventSortBy = eventsList.SortOrder
};
return allEvents.OrderBy(x => x.EventSortBy);
}
The GetEventIdsByEventDate() method should return an IEnumerable of strings, containing the Event Ids (like the method name implies):
public IEnumerable<string> GetEventIdsByEventDate(DateTime eventDate)
{
return (from feed in xmlDoc.Descendants("Show")
from ev in feed.Elements("Event")
where Convert.ToDateTime(ev.Attribute("Date").Value).ToShortDateString() == eventDate.ToShortDateString()
select feed.Attribute("Code").Value
).ToList();
}
Also, don't forget to rename the foo() method to a more suitable name (for example GetEventsByEventDate())
About your error:
GetAllEventsByDate returns an IEnumerable containing EventFeed objects, so when you use the "Contains" method, it expects an "EventFeed" object to compare to the objects in the list. Instead, you are passing it an f.EventShowCode, which I assume is an integer or something:
EventShowCode = eventsList.EventId
I believe what you're looking for is this:
public IEnumerable<EventFeed> foo(DateTime str)
{
var foo = from f in GetAllEventsFromDatabase()
where GetAllEventsByDate(str).Contains(f)
select f;
return foo;
}
Download LINQPad. It's free but the upgraded version provides Intellisense support. This app has helped me figure out some pretty complicated LINQ queries.