Use except() to exclude items from List - c#

Trying to use Except to exclude items from a list. However the following code is not working for me i.e. my list still includes the records that it should exclude. Is there anything obvious that I am doing wrong? Is there an issue with my loops? BTW the inverse of this code works i.e correct record is inserted when RunTime matches.
[HttpPost]
public JsonResult InsertActivities([FromBody] List<MemberData> customers)
{
var mData =_context.MemberData.Select(x => x.RunTime).ToList();
foreach (var item in mData)
{
var exclude = customers.Where(x => x.RunTime == item).ToList();
var list = customers.Except(exclude).ToList();
foreach (var data in list)
{
_context.MemberData.Add(data);
}
}
int insertedRecords = _context.SaveChanges();
return Json(insertedRecords);
}

Firstly be sure you have model design like below:
public class MemberData:IEquatable<MemberData>
{
public int Id { get; set; }
public string RunTime { get; set; }
public bool Equals(MemberData other)
{
if (other is null)
return false;
return this.RunTime == other.RunTime;
}
public override bool Equals(object obj) => Equals(obj as MemberData);
public override int GetHashCode() => (RunTime).GetHashCode();
}
Then change your code like below:
public JsonResult InsertActivities([FromBody] List<MemberData> customers)
{
//hard-coded the value...
//customers = new List<MemberData>()
//{
// new MemberData(){RunTime="aa"},
// new MemberData(){RunTime="bb"},
// new MemberData(){RunTime="ee"},
//}; //hard-coded the value...
var mData = _context.MemberData.ToList();
var list = customers.Except(mData);
foreach (var item in list)
{
foreach (var data in list)
{
_context.MemberData.Add(data);
}
}
int insertedRecords = _context.SaveChanges();
return Json(insertedRecords);
}
Note:
For inserting data to database successfully, if your model contains primary key, be sure the data's(after did Except operation) keys are not duplicated with the existing database data. Or you can just do like what I did in above code that do not set value for primary key.

Related

Simplest method to prove that the contents of two lists (containing objects) are equal

I am having a bit of a frustrating time finding a simple method to compare and prove that the contents of two lists are equal. I have looked at a number of solutions on stackoverflow but I have not been successful. Some of the solutions look like they will require a large amount of work to implement and do something that on the face of it to my mind should be simpler, but perhaps I am too simple to realize that this cannot be done simply :)
I have created a fiddle with some detail that can be viewed here: https://dotnetfiddle.net/cvQr5d
Alternatively please find the full example below, I am having trouble with the object comparison method (variable finalResult) as it's returning false and if the content were being compared I would expect the value to be true:
using System;
using System.Collections.Generic;
using System.Linq;
public class ResponseExample
{
public Guid Id { get; set; } = Guid.Parse("00000000-0000-0000-0000-000000000000");
public int Value { get; set; } = 0;
public string Initials { get; set; } = "J";
public string FirstName { get; set; } = "Joe";
public string Surname { get; set; } = "Blogs";
public string CellPhone { get; set; } = "0923232199";
public bool EmailVerified { get; set; } = false;
public bool CellPhoneVerified { get; set; } = true;
}
public class Program
{
public static void Main()
{
var responseOne = new ResponseExample();
var responseTwo = new ResponseExample();
var responseThree = new ResponseExample();
var responseFour = new ResponseExample();
List<ResponseExample> objectListOne = new List<ResponseExample>();
objectListOne.Add(responseOne);
objectListOne.Add(responseTwo);
List<ResponseExample> objectListTwo = new List<ResponseExample>();
objectListTwo.Add(responseThree);
objectListTwo.Add(responseFour);
bool result = objectListOne.Count == objectListTwo.Count();
Console.WriteLine($"Count: {result}");
bool finalResult = ScrambledEquals<ResponseExample>(objectListOne, objectListTwo);
Console.WriteLine($"Object compare: {finalResult}");
}
//https://stackoverflow.com/a/3670089/3324415
public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2)
{
var cnt = new Dictionary<T,
int>();
foreach (T s in list1)
{
if (cnt.ContainsKey(s))
{
cnt[s]++;
}
else
{
cnt.Add(s, 1);
}
}
foreach (T s in list2)
{
if (cnt.ContainsKey(s))
{
cnt[s]--;
}
else
{
return false;
}
}
return cnt.Values.All(c => c == 0);
}
}
As people in comments have pointed out this will not work as comparing a complex type by default compares whether the reference is the same. Field by field comparison will not work without implementing equality methods (and then you would need to overload GetHashCode and so on). See https://learn.microsoft.com/en-us/dotnet/api/system.object.equals?view=net-5.0
However, if you can use c# 9, which is what you have in the fiddle you can define the type as a record instead of class. Records have built in field by field comparison. See https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records#characteristics-of-records
So public class ResponseExample would become public record ResponseExample and your code works as you expect.
Use Enumerable.All<TSource>(IEnumerable<TSource>, Func<TSource,Boolean>) Method which Determines whether all elements of a sequence satisfy a condition.
Once you have initilized your two List
list1.All(x=>list2.Contains(x))
This works by ensuring that all elements in list2 are containted in list1 otherwise returns false
Your method as is will compare if the 2 lists contain the same objects. So it is returning false as there are 4 different objects. If you create your list like this, using the same objects, it will return true:
List<ResponseExample> objectListOne = new List<ResponseExample>();
objectListOne.Add(responseOne);
objectListOne.Add(responseTwo);
List<ResponseExample> objectListTwo = new List<ResponseExample>();
objectListTwo.Add(responseTwo);
objectListTwo.Add(responseOne);
To get a true value when the contents of the objects are the same you could serialize the objects into a json string like this:
public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2)
{
JavaScriptSerializer json = new JavaScriptSerializer();
var cnt = new Dictionary<string,
int>();
foreach (T _s in list1)
{
string s = json.Serialize(_s);
if (cnt.ContainsKey(s))
{
cnt[s]++;
}
else
{
cnt.Add(s, 1);
}
}
foreach (T _s in list2)
{
string s = json.Serialize(_s);
if (cnt.ContainsKey(s))
{
cnt[s]--;
}
else
{
return false;
}
}
return cnt.Values.All(c => c == 0);
}
If the performance is not a big deal, you can use Newtonsoft.Json. We will be able to compare different types of objects as well as run a deep equals check.
First install the package:
Install-Package Newtonsoft.Json
Here is the code snip:
public static bool DeepEqualsUsingJson<T>(IList<T> l1, IList<T> l2)
{
if (ReferenceEquals(l1, l2))
return true;
if (ReferenceEquals(l2, null))
return false;
if (l1.Count != l2.Count)
return false;
var l1JObject = l1.Select(i => JObject.FromObject(i)).ToList();
var l2JObject = l2.Select(i => JObject.FromObject(i)).ToList();
foreach (var o1 in l1JObject)
{
var index = l2JObject.FindIndex(o2 => JToken.DeepEquals(o1, o2));
if (index == -1)
return false;
l2JObject.RemoveAt(index);
}
return l2JObject.Count == 0;
}

Update the property of list items without "loop" and "if"

I am updating the property of the list items.
class Response
{
public string Name { get; set; }
public int Order { get; set; }
}
Here I want to update the Order of a List<Response> variable. As of now, I am looping through each item of the list and updating it.
List<Response> data = FromDb();
foreach (var item in data)
{
if(item.Name.Equals("A"))
{
item.Order=1;
}
if(item.Name.Equals("B"))
{
item.Order=2;
}
//Like this I have arround 20 conditions
}
The above code is working fine, but the problem is the Cognitive Complexity of the method is more than the allowed.
I tried something like below
data.FirstOrDefault(x => x..Equals("A")).Order = 1;
data.FirstOrDefault(x => x..Equals("B")).Order = 2;
//and more ...
In this code also null check is not in place, So if the searching string is not present in the list then again it will break.
If I add null check condition then again the complexity getting higher.
So here I want without any for loop or if, If I can update the Order of the list by using linq/lamda or anything else.
I don't know how you measure Cognitive Complexity and how much of it is allowed to be pushed out into other functions, but something like this makes the ordering quite declarative?
[Fact]
public void TestIt()
{
var data = FromDb().Select(SetOrder(
("A", 1),
("B", 2)
));
}
static Func<Response, Response> SetOrder(params (string Name, int Order)[] orders)
{
var orderByKey = orders.ToDictionary(x => x.Name);
return response =>
{
if (orderByKey.TryGetValue(response.Name, out var result))
response.Order = result.Order;
return response;
};
}
Addendum in response to comment:
In order to have a default value for unmatched names, the SetOrder could be changed to this:
static Func<Response, Response> SetOrder(params (string Name, int Order)[] orders)
{
var orderByKey = orders.ToDictionary(x => x.Name);
return response =>
{
response.Order =
orderByKey.TryGetValue(response.Name, out var result)
? result.Order
: int.MaxValue;
return response;
};
}

C# custom add in a List

I have the following list of strings :
var files = new List<string> {"file0","file1","file2","file3" };
I would like to be able to add new files to this list, but if the inserted file is present in the list, I would like to insert custom value that will respect the following format $"{StringToBeInserted}"("{SomeCounter}
For instance : try to add "file0" and "file0" is already I would like to insert "file0(1)". If I try again to add "file0" ... I would like to insert with "file0(2)" and so on ... Also, I would like to provide a consistency, for instance if I delete "file0(1)" ... and try to add again "item0" ... I expect that "item0(1)" to be added. Can someone help me with a generic algorithm ?
I would use a HashSet<string> in this case:
var files = new HashSet<string> { "file0", "file1", "file2", "file3" };
string originalFile = "file0";
string file = originalFile;
int counter = 0;
while (!files.Add(file))
{
file = $"{originalFile}({++counter})";
}
If you have to use a list and the result should also be one, you can still use my set approach. Just initialize it with your list and the result list you'll get with files.ToList().
Well, you should create your own custom class for it, using the data structure you described and a simple class that includes a counter and an output method.
void Main()
{
var items = new ItemCountList();
items.AddItem("item0");
items.AddItem("item1");
items.AddItem("item2");
items.AddItem("item0");
items.ShowItems();
}
public class ItemCountList {
private List<SimpleItem> itemList;
public ItemCountList() {
itemList = new List<SimpleItem>();
}
public void DeleteItem(string value) {
var item = itemList.FirstOrDefault(b => b.Value == value);
if (item != null) {
item.Count--;
if (item.Count == 0)
itemList.Remove(item);
}
}
public void AddItem(string value) {
var item = itemList.FirstOrDefault(b => b.Value == value);
if (item != null)
item.Count++;
else
itemList.Add(new SimpleItem {
Value = value,
Count = 1
});
}
public void ShowItems() {
foreach (var a in itemList) {
Console.WriteLine(a.Value + "(" + a.Count + ")");
}
}
}
public class SimpleItem {
public int Count {get; set;}
public string Value {get; set;}
}

Remove duplicate rows mvc

I have this method
Meeting is a class
Attendees is an ICollection in Meeting
Class
public partial class Meeting
{
public Meeting()
{
this.Attendees = new List<Attendees>();
}
public virtual ICollection<Attendees> Attendees{ get; set; }
[...]
Method Controller
private void RemoveRowsDuplicated(Meeting model)
{
if (model.Attendees != null)
{
foreach (var item in model.Attendees.GroupBy(x => x.UserName).Select(y => y.Last()))
{
context.Attendees.Remove(item);
}
}
}
The objective is remove duplicate Attendees with the same username in the table.
But the current method it deletes all records and keeps the duplicate
Where am I going wrong?
Correct version of your method will look like this:
private static void RemoveRowsDuplicated(Meeting model)
{
if (model.Attendees != null)
{
var duplicates = new List<Attendees>();
foreach (var item in model.Attendees.GroupBy(x => x.UserName).Where(x=>x.Count()>1))
{
duplicates.AddRange(item.Skip(1));
}
duplicates.ForEach(x=>context.Attendees.Remove(x));
}
}
You can try writing raw SQL and invoking via EF and return Attendees objects in a list.
var query = "Select * from Attendees group by username";
var attendeesList = dbContext.Database.SqlQuery<Attendees>(query).ToList<Attendees>();
As I can see you grouped elements by name and remove last item. So you remove unique elements.
Like this
private void RemoveRowsDuplicated(Meeting model)
{
if (model.Attendees != null)
{
var temporaryAtendees = new List<Attendees>();
foreach(var item in model.Attendees)
{
if (temporaryAtendees.Contains(item))
{
context.Attendees.Remove(item);
}
else
{
temporaryAtendees.Add(item);
}
}
}
}

how do I combine objects in order to display all at the same time as JSON?

In the code below, check the following line:
//here I need to put the object "nd" into a "bucket" so that I can finish the loop and then return EVERYTHING together.
My question is, how do I combine objects to return as JSON? The reason why I need to "combine" is because of the loop which assigns values to specific properties of this class. Once each class has been done getting property values, I need to return everything as JSON.
namespace X
{
public class NotificationsController : ApiController
{
public List<NotificationTreeNode> getNotifications(int id)
{
var bo = new HomeBO();
var list = bo.GetNotificationsForUser(id);
var notificationTreeNodes = (from GBLNotifications n in list
where n.NotificationCount != 0
select new NotificationTreeNode(n)).ToList();
foreach (var notificationTreeNode in notificationTreeNodes)
{
Node nd = new Node();
nd.notificationType = notificationTreeNode.NotificationNode.NotificationType;
var notificationList = bo.GetNotificationsForUser(id, notificationTreeNode.NotificationNode.NotificationTypeId).Cast<GBLNotifications>().ToList();
List<string> notificationDescriptions = new List<string>();
foreach (var item in notificationList)
{
notificationDescriptions.Add(item.NotificationDescription);
}
nd.notifications = notificationDescriptions;
//here I need to put the object "nd" into a "bucket" so that I can finish the loop and then return EVERYTHING together.
}
return bucket;
}
}
public class Node
{
public string notificationType
{
get;
set;
}
public List<string> notifications
{
get;
set;
}
}
}
You can simply add each item to a list as you're iterating through the source collection:
public List<Node> getNotifications(int id)
{
var bucket = new List<Node>(notificationTreeNodes.Count);
foreach (var notificationTreeNode in notificationTreeNodes)
{
Node nd = new Node();
...
bucket.Add(nd);
}
return bucket;
}

Categories