LINQ - Condition with .Contains() is not working as expected - c#

I cannot seem to get the desirable filtered result from my query.
Data
public class fdp_1115
{
public string Id{ get; set; }
public string Number{ get; set; }
public string Type{ get; set; }
}
List<fdp_1115> fdpList = new List<fdp_1115>
{
new fdp_1115 { Id = "1", Number = "Lot123", Type = "D14MWT" },
new fdp_1115 { Id = "2", Number = "Lot123", Type = "E12WBC7W1" }
};
List<string> searchValues = new List<string> { "MLE12WBC7W1 A R" };
LINQ:
var LocType = fdpList.FirstOrDefault(d => searchValues.Any(s => d.Type.Contains(s)));
if (LocType != null)
{
Console.WriteLine("Matching record found:");
Console.WriteLine($"Id: {LocType.Id}, Number: {LocType.Number}, Type: {LocType.Type}");
}
else
{
Console.WriteLine("No matching records found.");
}
The result I wanted is:
Matching record found:
Id: 2, Number: Lot123, Type: E12WBC7W1
But I got "No matching records found." which indicates that LocType == null.
I already tried trimming and ignoring case sensitive:
var LocType = fdpList.FirstOrDefault(d => searchValues.Any(s => d.Type.Contains(s.Trim().Replace(" ", ""))));
var LocType = fdpList.FirstOrDefault(d => searchValues.Any(s => d.Type.Contains(s, StringComparison.InvariantCultureIgnoreCase)));
But still no luck. Any idea how do I match "MLE12WBC7W1 A R" with "E12WBC7W1"?

You have your contains the other way around.
d.Type = "E12WBC7W1"
and
s = "MLE12WBC7W1 A R"
Then "E12WBC7W1" does not Contains "MLE12WBC7W1 A R"
It is the other way around.
var LocType = fdpList.FirstOrDefault(d => searchValues.Any(s => s.Contains(d.Type)));

Your current logic checks whether there is any object with Type value that contains the value for each string in the searchValues array.
From your requirement:
You want to filter the object that fulfills there is any string in searchValues containing the value of Type.
Thus it should be:
var LocType = fdpList.FirstOrDefault(d => searchValues.Any(s => s.Contains(d.Type)));

Related

how can I reduce 2 select to 1 select linq to gain performance?

my question is simple but I got stuck with something. Can you tell me how can I reduce 2 select into 1 select LINQ in c#? I am using CloudNative.CloudEvents NuGet package for cloud-native events.
var orderEvents = input
.Select(_ => new OrderDocument(_.Id, _.ToString()).ToOrderEvent())
.Select(_ =>
new CloudEvent()
{
Type = _.EventType,
Subject = _.Subject,
Source = _.Source,
Data = _
});
input is a parameter from cosmosDbTrigger it`s type : IReadOnlyList
OrderDocument.cs
public class OrderDocument
{
public string Id { get; private set; }
public string Json { get; private set; }
public OrderDocument(string id, string json)
{
Id = id;
Json = json;
}
public OrderEvent ToOrderEvent() => OrderEventHelper.ToOrderEvent(Json);
}
OrderEventHelper.cs
public static OrderEvent ToOrderEvent(string json)
{
ArgumentHelper.ThrowIfNullOrEmpty(json);
var orderEvent = JsonConvert.DeserializeObject<OrderEvent>(json);
var eventDefinition = OrderEvents.EventDefinitions.SingleOrDefault(_ => _.EventType == orderEvent.EventType);
return eventDefinition == null
? orderEvent
: new OrderEvent(
orderEvent.Id,
orderEvent.Source,
orderEvent.EventType,
orderEvent.Subject,
orderEvent.DataContentType,
orderEvent.DataSchema,
orderEvent.Timestamp,
JsonConvert.DeserializeObject(orderEvent.Payload.ToString(), eventDefinition.PayloadType),
orderEvent.TraceId);
}
linq extensions are basically for loops in the background. If you want to perform multiple actions against a list, perhaps making your own simple for loop where you can manage that yourself would work.
Your code:
var orderEvents = input
.Select(_ => new OrderDocument(_.Id, _.ToString()).ToOrderEvent())
.Select(_ =>
new CloudEvent()
{
Type = _.EventType,
Subject = _.Subject,
Source = _.Source,
Data = _
});
could be changed to:
// our result set, rather than the one returned from linq Select
var results = new List<CloudEvent>();
foreach(var x in input){
// create the order event here
var temporaryOrderEvent = new OrderDocument(x.Id, x.ToString()).ToOrderEvent();
// add the Cloud event to our result set
results.Add(new CloudEvent()
{
Type = temporaryOrderEvent .EventType,
Subject = temporaryOrderEvent .Subject,
Source = temporaryOrderEvent .Source,
Data = temporaryOrderEvent
});
}
where you then have a result list to work with.
If you wanted to keep it all in linq, you could instead perform all of your logic in the first Select, and ensure that it returns a CloudEvent. Notice here that you can employ the use of curly brackets in the linq statement to evaluate a function rather than a single variable value:
var orderEvents = input
.Select(x =>
{
// create the order event here
var temporaryOrderEvent = new OrderDocument(x.Id, x.ToString()).ToOrderEvent();
// return the Cloud event here
return new CloudEvent()
{
Type = temporaryOrderEvent .EventType,
Subject = temporaryOrderEvent .Subject,
Source = temporaryOrderEvent .Source,
Data = temporaryOrderEvent
};
});
How about putting conversion to OrderEvent and using ToCloudEvent in the same Select?
var orderEvents = input
.Select(_ => new OrderDocument(_.Id, _.ToString()).ToOrderEvent().ToCloudEvent())
public class OrderEvent
{
public CloudEvent ToCloudEvent()
{
new CloudEvent()
{
Type = this.EventType,
Subject = this.Subject,
Source = this.Source,
Data = this
};
}
}

How to group by list of values and then count the amount of entries per value

I have a list of objects. Each object contains a list of categories as a comma delimited string.
I want to know how many objects i have for each category. For this i think i need to group by the categories and then count the entries - however i can't wrap my head around grouping by a list.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
class MyDto
{
public string Name { get; set; }
public List<string> Categories => CategoriesString
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList();
public string CategoriesString { get; set; }
public override string ToString()
{
return Name + ": " + CategoriesString;
}
}
public class Program
{
public static void Main()
{
var dtos = new MyDto[]
{
new MyDto() { Name = "Dto 1", CategoriesString = "DELIVERY"},
new MyDto() { Name = "Dto 2", CategoriesString = "DELIVERY , DAMAGE"},
new MyDto() { Name = "Dto 3", CategoriesString = "DAMAGE"},
new MyDto() { Name = "Dto 4", CategoriesString = "DAMAGE , DELIVERY"},
new MyDto() { Name = "Dto 5", CategoriesString = "DELIVERY"},
};
var res = dtos.GroupBy(c => c.Categories).Select(c => new { c.Key, amt = c.Count() });
foreach (var c in res)
{
Console.WriteLine(c.Key + " - " + c.amt);
}
// Should return:
// DELIVERY - 4
// DAMAGE - 3
}
}
https://dotnetfiddle.net/bowYb4
The sample above is just to demonstrate the issue and does not actually give the desired result(s). I'm using data objects coming from a database with EF core. I'm aware that what im trying to do won't translate to SQL - I'm doing this client-side and that is fine.
One option is to use SelectMany to flatten categories and transform the dtos in key-value pairs (I use valu tuples to store them):
var res = dtos
.SelectMany(dto => dto.Categories.Select(c => (Cat: c, dto.Name)))
.GroupBy(c => c.Cat)
.Select(c => new { c.Key, amt = c.Count() });
An alternative solution could be the following:
var lookup = dtos
.Select(c => c.Categories) //retrieve the already split values
.SelectMany(c => c) //flatten the IEnumerable<List<string> to IEnumerable<string>
.ToLookup(c => c, c => c); //group the same values
foreach (var item in lookup)
{
Console.WriteLine($"{item.Key} - {item.Count()}");
}
The difference between GroupBy and ToLookup is that the former is executed in a deferred way, while the latter is executed immediately.

C# - String to list used in Linq Where Any statement

I would like to use this string as a filter to remove some Ids in a linq query
public class ProductKitMakerDto
{
public int Id { get; set; }
public string TitleShort { get; set; }
public string Media { get; set; }
}
[HttpPost]
public ActionResult KitItemSelect(string culture)
{
string productMakerIds = "4174,2196,2201,2460,2508,2204";
//create a list
var productMakerList = new List<ProductKitMakerDto>();
foreach (int i in productMakerIds)
{
productMakerList.Add(new ProductKitMakerDto { Id = i });
}
var itemselects = (from p in _context.Products
where p.Matrix == 2400
select new ProductKitMakerDto()
{
Id = p.Id,
TitleShort = culture == "de" ? p.TitleShortDe :
culture == "fr" ? p.TitleShortFr :
p.TitleShortEn,
Media = "/img/" + p.Photo,
}).ToList();
//From this query I get 40 results.
//Then I want to remove the ones from the list:
//itemselects = itemselects.Where(i => !productMakerList.Any(pml =>pml.Id == i.Id));
//1st (above) I get an Error CS0266 asking for explicit cast. So aplly the modification
itemselects = (List<ProductKitMakerDto>)itemselects.Where(i => !productMakerList.Any(pml =>pml.Id == i.Id));
return Json(itemselects, JsonRequestBehavior.AllowGet);
}
I get 500 (Internal Server Error) - xhr.send( options.hasContent && options.data || null );
I guess the list is empty.
Any idea? Thanks
this does not work
string productMakerIds = "4174,2196,2201,2460,2508,2204";
var productMakerList = new List<ProductKitMakerDto>();
foreach (int i in productMakerIds)
{
productMakerList.Add(new ProductKitMakerDto { Id = i });
}
because you need to split on comma first and parse the string to int:
foreach (string i in productMakerIds.Split(',')) // and parse i to int with int.Parse
but since it's a string literal, initialize it correctly in the first place. Don't use a List<ProductKitMakerDto> because you just need a List<int>, then you can use Contains:
var productMakerList = new List<int>
{
4174, 2196, 2201, 2460, 2508 , 2204
};
you can not cast to a list if it's not a list and Enumerable.Where does not return one:
itemselects = (List<ProductKitMakerDto>)itemselects.Where(i => !productMakerList.Any(pml =>pml.Id == i.Id));
you need to append ToList after the Where
itemselects = itemselects
.Where(i => !productMakerList.Any(pml =>pml.Id == i.Id))
.ToList();
but as mentioned, you could also use this Where before you create that list the first time, so include the condition witha Contains which should be supported:
var itemselects = (from p in _context.Products
where p.Matrix == 2400
&& !productMakerList.Contains(p.Id)
select new ProductKitMakerDto()
{
Id = p.Id,
TitleShort = culture == "de"
? p.TitleShortDe
: culture == "fr" ? p.TitleShortFr : p.TitleShortEn,
Media = "/img/" + p.Photo,
}).ToList();
foreach (string i in productMakerIds.Split(','))
{
productMakerList.Add(new ProductKitMakerDto { Id = int.Parse(i) });
}

Merge two Lists of same type with diff values and avoid duplicates

I have two lists of same type with different key value pairs,
List1 has "isPermanent = true" and List2 has false value and also
List1 has an extra key "nextVacationDate".
Im trying to do union of these as below but im afraid I will still get the duplicates because of different values. I need to merge both lists in to one list and order by List1 first (Permanent employees first)..is there a better way to do this using LINQ?
public newList1 List1(string abcd)
{
var result = serviceMethod1(abcd);
var newList1 = new List<emp>();
if (result == null) return null;
newList.AddRange(
result.Select(x => new Model
{
firstName = x.FName,
secondName = x.SName,
address = x.Address,
employeeId = x.EmpId,
isPermanent = true,
nextVacationDate =x.VacDt,
salary = x.Bsalary
}));
return newList1;
}
public newList2 List2(string defg)
{
var result = serviceMethod2(defg);
var newList2 = new List<emp>();
if (result == null) return null;
newList.AddRange(
result.Select(x => new Model
{
firstName = x.FName,
secondName = x.SName,
address = x.Address,
employeeId = x.EmpId,
isPermanent = false,
salary = x.Bsalary
}));
return newList2;
}
private List<emp> EmployyeList(List<emp> newList1, List<emp> newList2)
{
var sortedEmpList1 = newList1.OrderBy(i => i.Fname);
var sortedEmpList2 = newList2.OrderBy(i => i.Fname);
List<MeterModel> combinedList = newList1.Union(newList2) as List<emp>;
return combinedList;
}
You can filter the 2nd list to avoid duplicates:
newList1.Union(newList2.Where(emp2 => !newList1.Any(emp1 => emp1.employeeId == emp2.employeeId)))

default values for IEnumerable collection when length is zero

I have a IEnumerable collection:
public IEnumerable<Config> getConfig(string CID, string name)
{
var raw = db.ap_GetInfo(CID, name);
foreach (var item in raw.ToList().Where(x => x.Name!= null))
{
var x = raw.Count();
yield return new Config
{
Name = item.Name.ToString(),
Value = item.Value.ToString(),
};
}
}
The problem I am facing is that if this return a length of zero I am then unable to set the attributes to something else, If I have a response of length 1 the attributes are set from the database, however length zero I want to set a dfault value for Name and Value.
A LINQ solution - this returns the default if there are no items in the enumerable using DefaultIfEmpty:
public IEnumerable<Config> GetConfig(string CID, string name)
{
return db.ap_GetInfo(CID, name)
.Where(x => !string.IsNullOrEmpty(x.Name))
.Select(x => new Config
{
Name = x.Name.ToString(),
Value = x.Value.ToString(),
})
.DefaultIfEmpty(new Config
{
Name = "DefaultName",
Value = "DefaultValue"
});
}
If I understood your question correctly, you want to replace the case
0 results
with
1 result with a default value.
If that is correct, the easiest way is to fix this in the calling function:
var result = getConfig(...).ToList();
if (!result.Any())
{
result = new[] {new Config {Name = "DefaultName", Value = "DefaultValue"}};
}
Obviously, you can wrap this in a new function:
public IEnumerable<ClubConfig> getConfigOrDefault(string CID, string name)
{
var result = getConfig(CID, name).ToList();
if (result.Any())
return result;
else
return new[] {new Config {Name = "DefaultName", Value = "DefaultValue"}};
}
To check if your query did return any elements use Any-method.
public IEnumerable<ClubConfig> getConfig(string CID, string name)
{
var raw = db.ap_GetInfo(CID, name);
if (!raw.Any()) return new[] {
ClubConfig
{
Name = "defaultName",
Value = "defaultValue"
}};
foreach (var item in raw.Where(x => !string.IsNullOrEmpty(x.Name))
{
yield return new ClubConfig
{
Name = item.Name.ToString(),
Value = item.Value.ToString(),
};
}
}
EDIT: You can also omit the ToList from your input.
You can do this using LINQ and can maintain lazy evaluation of the IEnumerable result as follows:
public IEnumerable<ClubConfig> getConfig(string CID, string name)
{
var raw = db.ap_GetInfo(CID, name);
return raw.Where(x => !string.IsNullOrEmpty(x.Name))
.Select(item => new ClubConfig
{
Name = item.Name.ToString(),
Value = item.Value.ToString(),
})
.DefaultIfEmpty(new ClubConfig { Name = "n", Value="v" });
}

Categories

Resources