Finding and comparing two objects, while avoiding NullReferenceException - c#

I am trying to compare an original object to an updated one to find if they are different from each other. The compare logic should be as follows:
If neither object a nor b contains an object in SomeList, which has EType equal to EnumType.FooType, they should be evaluated as equals.
If only a xor b contains an object in SomeList, which has EType equal to EnumType.FooType, they should be evaluated as different.
If both objects contain an object as mentioned above, and the property Number is equal on both objects, then objects a and b should be evaluated as equals.
The following code solves the task, however it is long and bulky, so I ask if it can be shortened and made 'prettier'?
var a = original.SomeList.FirstOrDefault(p => p != null && p.EType == EnumType.FooEnum);
var b = updated.SomeList.FirstOrDefault(p => p != null && p.EType == EnumType.FooEnum);
var bEqual = false;
if (a == null && b == null)
bEqual = true;
else if (a != null && b != null)
bEqual = a.Number == b.Number;

return (a == null) ? (b == null) :
(b != null && a.Number == b.Number);
A better solution is to create a full set of equality functions and operators. This means implementing IEquatable and override Object.Equals(object). A total of four small functions would enable you to write:
return a == b;
MSDN has a good article about overriding equality functions.
How to: Define Value Equality for a Type (C# Programming Guide)

In C# 6.0 you can use the null propagation operator:
var a = original.SomeList.FirstOrDefault(p => p != null && p.EType == EnumType.FooEnum);
var b = updated.SomeList.FirstOrDefault(p => p != null && p.EType == EnumType.FooEnum);
return a?.Number == b?.Number;

Well straight off the bat you can do this
var a = original.SomeList.FirstOrDefault(p => p != null && p.EType == EnumType.FooEnum);
var b = updated.SomeList.FirstOrDefault(p => p != null && p.EType == EnumType.FooEnum);
var bEqual = false;
if (a == null && b == null)
bEqual = true;
else
bEqual = a.Number == b.Number;
You are already checking if they are null so if they are null then you are setting your bEqual to true; and if they aren't null then you can just perform the normal code below.
Now without testing your code I would think you could do something like this
if(original.SomeList.FirstOrDefault(p => p != null && p.EType == EnumType.FooEnum) != null && updated.SomeList.FirstOrDefault(p => p != null && p.EType == EnumType.FooEnum) != null)
bEqual = original.Number == updated.Number;
Something along those lines should be sufficient if I was at home I would test it for you and make sure but it should be close enough.

You can reduce it with conditional operator :
var a = original.SomeList.FirstOrDefault(p => p != null && p.EType == EnumType.FooEnum);
var b = updated.SomeList.FirstOrDefault(p => p != null && p.EType == EnumType.FooEnum);
bool bEqual = (a == null && b == null)? true :
(a != null && b != null)? a.Number == b.Number : false;

Related

I want to write LINQ where some condition may be null or not

I want to retrieve some data from database based on some condition, for this I'm using LINQ. But problem is that I don't know how to write LINQ when some condition may be null.
from x in _db.AirWorkOrder
join c in _db.Clients on x.ClientId equals c.Id
where x.CreatedOn >= model.StartDate && x.CreatedOn <= model.EndDate && x.ClientId == model.ClientId && x.Type == model.Type && x.WorkOrderStatus == model.Status
select new DateWisedReportItemModel
I want if the clientID, type, status have null value then it will take all the values saved in the DB. and if there are some values provided in it, then it will work according to the condition.
I presume you're asking how to wildcard things that are null. You need to make the particular clause true if your model value is null. You could do this by saying things like:
(dbcolumn == modelvalue || modelvalue == null) && ...
Or like
(dbcolumn == modelvalue == null ? dbvalue : modelvalue) && ...
Or like
(dbcolumn == modelvalue ?? dbvalue)
For example:
from x in _db.AirWorkOrder
join c in _db.Clients on x.ClientId equals c.Id
where
(x.CreatedOn >= model.StartDate ?? x.CreatedOn) &&
(x.CreatedOn <= model.EndDate == null ? x.CreatedOn : model.EndDate) &&
(x.ClientId == model.ClientId || model.ClientId == null) &&
...
I prefer the first as it is the most easy to understand. Also note carefully that it is the only one that will return the row if the row value in the db is null, because to a database null is never equal to null
I want if there is no condition given in ClientId, type and status then only date(this is mandatory) filter will apply, and if there is condition in these only then these(clientId, type and status) conditions will work
It might be most simple, in terms of code readability to do like:
var baseQuery = from x in _db.AirWorkOrder
join c in _db.Clients on x.ClientId equals c.Id
IEnumerable<...> result;
if(model.ClientId == null && model.Type == null && model.Status == null){
//search on date only
result = baseQuery.Where(x.CreatedOn >= model.StartDate && x.CreatedOn <= model.EndDate);
} else {
//search on no null client/type/status only
result = baseQuery.Where(x =>
(x.ClientId == model.ClientId || model.ClientId == null) &&
(x.Type == model.Type || model.Type == null) &&
(x.WorkOrderStatus == model.Status || model.Status == null);
}

ASP.NET MVC5 Entity Framework 6 get bool = true and bool = false LINQ

I have a table that I am filtering on.
There is a filter for values 'include' which can be true or false.
I have a filter that has 3 options: true, false, & all.
So, when the filter is true, it should return rows where include = 'true'; when the filter is 'false', return where include = false; and when 'all' return where include = true or false.
Here is my code, that is not working, but I think it should be.
private ICollection<AggregationEntityViewModel> getEntities(AggregationPracticeDetailsViewModel apdvm)
{
bool? filterInclude = Convert.ToBoolean(apdvm.Filter_IncludeValue);
var a = (from e in _repository.GetAll<Entity>()
where e.include == filterInclude != null ? (bool)filterInclude : (true || false)
select e
return a;
}
It is currently returning 0 rows when filter is set to 'All' or 'False', and returning all rows when set to 'Yes'.
FYI, I have ommitted lots of code for clarity's sake.
Please help...thanks!
*EDIT: I've displayed all the code, so you can see why I want to keep it all in linq query. Thanks for all the offered solutions. I see that most solutions involve using Linq Extension methods. Is there anyway to do it in inline linq query? *
bool? filterInclude = Convert.ToBoolean(apdvm.Filter_IncludeValue);
var a = (from e in _repository.GetAll<Entity>()
from u in e.Users
where (e.AuditQuestionGroupId != null ? e.AuditQuestionGroupId : 0) == this.LoggedInEntity.AuditQuestionGroupId
&& e.BatchNumber != null && e.BatchNumber.StartsWith(apdvm.Filter_BatchNumber == null ? "" : apdvm.Filter_BatchNumber)
&& e.Name != null && e.Name.ToLower().StartsWith(apdvm.Filter_EntityName.ToLower())
&& e.EntityState != null && e.EntityState.ToLower().Contains(apdvm.Filter_StateValue == null ? "" : apdvm.Filter_StateValue.ToLower())
&& u.NIAMembershipId != null && u.NIAMembershipId.Contains(apdvm.Filter_MemberNo == null ? "" : apdvm.Filter_MemberNo)
from p in e.PracticeProfiles.DefaultIfEmpty()
join ea in _repository.GetAll<EntityAggregate>() on e.EntityId equals ea.EntityId into eas
from ea in eas.DefaultIfEmpty()
where ea.include == filterInclude != null ? (bool)filterInclude : (true || false)
group e by new { entity = e, profile = p, ea = ea } into newGroup
orderby newGroup.Key.entity.Name
select new AggregationEntityViewModel()
{
Id = newGroup.Key.ea == null ? 0 : newGroup.Key.ea.Id,
EntityId = newGroup.Key.entity.EntityId,
Include = newGroup.Key.ea == null ? (true || false) : (bool)newGroup.Key.ea.include,
BHAddress = newGroup.Key.profile == null || newGroup.Key.profile.soloOffice == null ? false : (bool)newGroup.Key.profile.soloOffice,
Incorporated = newGroup.Key.profile == null || newGroup.Key.profile.company == null ? false : (bool)newGroup.Key.profile.company,
MajorityOwned = newGroup.Key.profile == null || newGroup.Key.profile.capital == null ? false : (bool)newGroup.Key.profile.capital,
MajorityVoting = newGroup.Key.profile == null || newGroup.Key.profile.votingRights == null ? false : (bool)newGroup.Key.profile.votingRights,
Name = newGroup.Key.entity.Name,
Partnership = newGroup.Key.profile == null || newGroup.Key.profile.partnership == null ? false : (bool)newGroup.Key.profile.partnership,
PublicAccountant = newGroup.Key.profile == null || newGroup.Key.profile.publicAccountant == null ? false : (bool)newGroup.Key.profile.publicAccountant,
Trust = newGroup.Key.profile == null || newGroup.Key.profile.operatingTrust == null ? false : (bool)newGroup.Key.profile.operatingTrust,
TrustDeed = newGroup.Key.profile == null || newGroup.Key.profile.deed == null ? false : (bool)newGroup.Key.profile.deed
}).ToList();
return a;
Convert.ToBoolean returns bool, not bool?, so there is no way filterInclude != null is true.
You should use following pattern instead of ternary operator within where clause:
var query = _repository.GetAll<Entity>();
if (apdvm.Filter_IncludeValue == "true")
query = query.Where(x => x.include == true);
else if (apdvm.Filter_IncludeValue == "false")
query = query.Where(x => x.include == false);
return query;
I assumed apdvm.Filter_IncludeValue is a string (and that's why you tried to call Convert.ToBoolean on it).
You could use
private ICollection<AggregationEntityViewModel> getEntities(
AggregationPracticeDetailsViewModel apdvm)
{
bool? filterInclude = apdvm.Filter_IncludeValue.ConvertToNullable<bool>();
var a = (from e in _repository.GetAll<Entity>()
where !filterInclude.HasValue || ea.include == filterInclude.Value
select new AggregationEntityViewModel()
{
Include = newGroup.Key.ea == null
? (true || false)
: (bool)newGroup.Key.ea.include,
}
return a;
}
just remove your (true||false) and add filterInclude == null in the where
For Nullable Value (taken from Convert string to nullable type (int, double, etc...))
public static T? ConvertToNullable<T>(this String s) where T : struct
{
try
{
return (T?)TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(s);
}
catch (Exception)
{
return null;
}
}
There is an other solution:
var query = from e in _repository.GetAll<Entity>();
if (filterInclude.HasValue)
{
// when filterInclude is null (it means **ALL**),
// do not filter otherwise - check the flag
query = query.Where(entity => entity.Include == filterInclude.Value);
}
// or one-line:
// query = query.Where(entity => filterInclude == null
// || entity.Include == filterInclude.Value);
var a = query.Select(entity => new AggregationEntityViewModel { .... });
return a;
Other problem is that Convert.ToBoolean never returns null. You should create own method to parse apdvm.Filter_IncludeValue.
In order to convert to nullable type, you colud use the generic method:
public static Nullable<T> ToNullable<T>(this string s) where T: struct
{
Nullable<T> result = new Nullable<T>();
try
{
if (!string.IsNullOrEmpty(s) && s.Trim().Length > 0)
{
TypeConverter conv = TypeDescriptor.GetConverter(typeof(T));
result = (T)conv.ConvertFrom(s);
}
}
catch { }
return result;
}
Source.
Usage:
var filterInclude = apdvm.Filter_IncludeValue.ToNullable<bool>();
You can make it easier with fluent syntax like this:
private ICollection<AggregationEntityViewModel> getEntities(AggregationPracticeDetailsViewModel apdvm)
{
var query = _repository.GetAll<Entity>();
if(apdvm.Filter_IncludeValue != 'all')
{
var value = Convert.ToBoolean(apdvm.Filter_IncludeValue);
query = query.Where(q => q.include == value)
}
return query.Select(q => new AggregationEntityViewModel {...}).ToArray();
}
no need to evaluate string to nullable bool or smth. Same as no need to do strange boolean expressions.

Replacing several nodes in the same tree, using SyntaxNode.ReplaceNode

I'm currently working on three-way merging on syntax trees using Roslyn. I have a matching between all children on a a ClassDeclerationSyntax node, and want to perform a merge on the children, and then create a new tree based on that merge.
O is the input ClassDeclerationSyntax, and matching has three members (A, O, B) of the type MemberDeclerationSyntax.
var updated = O;
foreach (var m in matching)
{
if (m.A != null && m.B != null && m.O != null) {
var merge = Merge(m.A, m.O, m.B);
var oldUpdated = updated;
updated = updated.ReplaceNode(m.O, merge);
}
else if (m.A == null && m.O == null && m.B != null)
updated = updated.AddMembers(m.B);
else if (m.A != null && m.O == null && m.B == null)
updated = updated.AddMembers(m.A);
}
This does not work. In the second iteration ReplaceNode returns a completely unmodified node (oldUpdated == updated is true).
It seems that after the first iteration of the loop, all children have been reconstructed as new objects, and the original children-objects stored in my matching can no longer be found in the children list (updated.ChildNodes().Where(x => x == m.O) is empty).
What would a good way be to do this?
My current approach:
var updateMember = new Dictionary<MemberDeclarationSyntax, MemberDeclarationSyntax>();
var addMembers = new List<MemberDeclarationSyntax>();
foreach (var m in matching) {
if (m.A != null && m.B != null && m.O != null) {
var mergeChild = Merge(m.A, m.B, M.O);
updateMember.Add(m.O, child);
}
else if (m.A == null && m.O == null && m.B != null)
addMembers.Add(m.B);
else if (m.A != null && m.O == null && m.B == null)
addMembers.Add(m.A);
}
var merged = O.ReplaceNodes(updateMember.Keys.AsEnumerable(), (n1, n2) =>
{
return updateMember[n1];
}).AddMembers(addMembers.ToArray());

EF Non-static method requires a target

I've serious problems with the following query.
context.CharacteristicMeasures
.FirstOrDefault(cm => cm.Charge == null &&
cm.Characteristic != null &&
cm.Characteristic.Id == c.Id &&
cm.Line != null &&
cm.Line.Id == newLine.Id &&
cm.ShiftIndex != null &&
cm.ShiftIndex.Id == actShiftIndex.Id &&
(newAreaItem == null ||
(cm.AreaItem != null &&
cm.AreaItem.Id == newAreaItem.Id)));
I get a TargetException: Non-static method requires a target when newAreaItem is null.
If newAreaItem is not null I get an NotSupportedException: Unable to create a constant value of type 'PQS.Model.AreaItem'. Only primitive types or enumeration types are supported in this context.
Things I've already checked if they're null:
c, newLine, actShiftIndex all 3 variables are not null and the Id is accessible.
I dont get it... please help.
If u need more information.. dont hesitate to ask...
UPDATE
I could eliminate the NotSupportedException, but I still got the TargetException when my newAreaItemIsNull is true.. :/
bool newAreaItemIsNull = (newAreaItem == null);
var mc = context.CharacteristicMeasures
.FirstOrDefault(cm => cm.Charge == null &&
cm.Characteristic != null &&
cm.Characteristic.Id == c.Id &&
cm.Line != null &&
cm.Line.Id == newLine.Id &&
cm.ShiftIndex != null &&
cm.ShiftIndex.Id == actShiftIndex.Id &&
(newAreaItemIsNull ||
(cm.AreaItem != null &&
cm.AreaItem.Id == newAreaItem.Id)));
UPDATE
I finally did it. It seems that the query parse can't parse my newAreaItem(IsNull) because it's not in the DB model somehow !?
I have to split my queries..
bool newAreaItemIsNull = (newAreaItem == null);
MeasureCharacteristic mc;
if (newAreaItemIsNull)
mc = context.CharacteristicMeasures
.FirstOrDefault(cm => cm.Charge == null &&
cm.Characteristic != null &&
cm.Characteristic.Id == c.Id &&
cm.Line != null &&
cm.Line.Id == newLine.Id &&
cm.ShiftIndex != null &&
cm.ShiftIndex.Id == actShiftIndex.Id);
else
mc = context.CharacteristicMeasures
.FirstOrDefault(cm => cm.Charge == null &&
cm.Characteristic != null &&
cm.Characteristic.Id == c.Id &&
cm.Line != null &&
cm.Line.Id == newLine.Id &&
cm.ShiftIndex != null &&
cm.ShiftIndex.Id == actShiftIndex.Id &&
cm.AreaItem != null &&
cm.AreaItem.Id == newAreaItem.Id);
Does someone know a better solution?
Try moving newAreaItem == null outside of the query
bool newAreaItemIsNull = (newAreaItem == null);
and replace newAreaItem == null with newAreaItemIsNull in query.
Query parser can only operate with the objects in the database, and newAreaItem is not one of them.
I had the exact same problem as you have when newAreaItem == null is true.
The problem comes from the fact that the item used in the LINQ cannot be null. Thus, when newAreaItem == null is true it means that newAreaItem is null and this leads to the error being thrown.
All you can do in my opinion is, after checking newAreaItem == null, to set the newAreaItem to a new empty object of that type if newAreaIteam is null. The newAreaItemIsNull condition will still be in place, thus the
(cm.AreaItem != null && cm.AreaItem.Id == newAreaItem.Id)
in your code below will still not be evaluated if newAreaItem is null.
context.CharacteristicMeasures.
FirstOrDefault(cm => cm.Charge == null &&
cm.Characteristic != null && cm.Characteristic.Id == c.Id &&
cm.Line != null && cm.Line.Id == newLine.Id &&
cm.ShiftIndex != null && cm.ShiftIndex.Id == actShiftIndex.Id &&
(newAreaItem == null ||
(cm.AreaItem != null && cm.AreaItem.Id == newAreaItem.Id)));

Conditional WHERE in LINQ

Hi any suggestions on building a LINQ statement based on search criteria?
I'll be passing in an instance of a 'SearchCriteria' class with all parameters nullable.
I then want to
if (sc.a != null)
// add to where
if (sc.b != null)
// add to where
The key thing is these are to be ORs not ANDs.
Any tips?
And for bonus points I'd like to use 'contains' on an int? but I can only get equals or not equals.
Try:
.Where(x =>
(x.a != null ? x.a == a : false) &&
(x.b != null ? x.b == b : false));
or
.Where(x =>
(x.a != null && x.a == a) ||
(x.b != null && x.b == b));
Also:
.Where(x => new int[] { 1, 2, 3 }.Contains(x.i));

Categories

Resources