compare properties in classes of list in class - c#

What I've got are two classes which each contain Lists of Classes with propperties of different types. The first list is an updated version of the second and i need to find all differences (deleted/added classes in lists and updated classes).
public class ClassOfKb
{
public List<Data> KbData {get;set;}
public List<Info> KbInfo {get;set;}
}
class Data
{
public Guid ID {get;set}
public byte[] file {get;set}
public string name {get;set}
}
class Info
{
public Guid ID {get;set}
public string text {get;set}
public DateTime date {get;set}
}
ClassOfKb KbA = new ClassOfKb();
ClassOfKb KbB = new ClassOfKb();
first KbA and KbB will be filled from the same DataSet, then i delete, add and modify some of KbA Child-Classes.
now i need to compare KbA with KbB to find out where the differences are. i need the ID of deleted or added classes in KbA and the exact changes of modified Child-Classes properties. How would i do this? Preffered with Linq.

I suggest that create two comparers one for Data and one for Info
class DataComparer : IEqualityComparer<Data>
{
public bool Equals(Data x, Data y)
{
//logic to compare x to y and return true when they are equal
}
public int GetHashCode(Data d)
{
//logic to return a hash code
}
}
class InfoComparer : IEqualityComparer<Info>
{
public bool Equals(Info x, Info y)
{
//logic to compare x to y and return true when they are equal
}
public int GetHashCode(Info i)
{
//logic to return a hash code
}
}
The you can use Intersect and Except LINQ methods
IEnumerable<Data> DataInAandNotInB = KbA.KbData.Except(KbB.KbData,new DataComparer());
IEnumerable<Info> InfoInAandInB = KbA.KbInfo.Intersect(KbB.KbInfo,new InfoComparer ());

For simplicity, I skipped comparison of the byte array and DateTime data membes, only left the IDs and the string data members, but to add them you will need some small modification.
The test is very-very basic, but shows all three of the changes options:
static void Main(string[] args)
{
ClassOfKb KbA = new ClassOfKb();
ClassOfKb KbB = new ClassOfKb();
// Test data --------
Data data1 = new Data() { ID = Guid.NewGuid(), name = "111" };
Data data2 = new Data() { ID = Guid.NewGuid(), name = "222" };
Data data2_changed = new Data() { ID = data2.ID, name = "222_changed" };
Data data3 = new Data() { ID = Guid.NewGuid(), name = "333" };
Info info1 = new Info() { ID = Guid.NewGuid(), text = "aaa" };
Info info2 = new Info() { ID = Guid.NewGuid(), text = "bbb" };
Info info2_changed = new Info() { ID = info2.ID, text = "bbb_changed" };
Info info3 = new Info() { ID = Guid.NewGuid(), text = "ccc" };
KbA.KbData.Add(data1);
KbA.KbData.Add(data2);
KbA.KbInfo.Add(info1);
KbA.KbInfo.Add(info2);
KbB.KbData.Add(data2_changed);
KbB.KbData.Add(data3);
KbB.KbInfo.Add(info2_changed);
KbB.KbInfo.Add(info3);
// end of test data ---------
// here is the solution:
var indexes = Enumerable.Range(0, KbA.KbData.Count);
var deleted = from i in indexes
where !KbB.KbData.Select((n) => n.ID).Contains(KbA.KbData[i].ID)
select new
{
Name = KbA.KbData[i].name,
KbDataID = KbA.KbData[i].ID,
KbInfoID = KbA.KbInfo[i].ID
};
Console.WriteLine("deleted:");
foreach (var val in deleted)
{
Console.WriteLine(val.Name);
}
var added = from i in indexes
where !KbA.KbData.Select((n) => n.ID).Contains(KbB.KbData[i].ID)
select new
{
Name = KbB.KbData[i].name,
KbDataID = KbB.KbData[i].ID,
KbInfoID = KbB.KbInfo[i].ID
};
Console.WriteLine("added:");
foreach (var val in added)
{
Console.WriteLine(val.Name);
}
var changed = from i in indexes
from j in indexes
where KbB.KbData[i].ID == KbA.KbData[j].ID &&
(//KbB.KbData[i].file != KbA.KbData[j].file ||
KbB.KbData[i].name != KbA.KbData[j].name ||
//KbB.KbInfo[i].date != KbA.KbInfo[j].date ||
KbB.KbInfo[i].text != KbA.KbInfo[j].text
)
select new
{
Name = KbA.KbData[j].name,
KbDataID = KbA.KbData[j].ID,
KbInfoID = KbA.KbInfo[j].ID
};
Console.WriteLine("changed:");
foreach (var val in changed)
{
Console.WriteLine(val.Name);
}
Console.ReadLine();
}
}
public class ClassOfKb
{
public List<Data> KbData = new List<Data>();
public List<Info> KbInfo = new List<Info>();
}
public class Data
{
public Guid ID { get; set; }
public byte[] file { get; set; }
public string name { get; set; }
}
public class Info
{
public Guid ID { get; set; }
public string text { get; set; }
public DateTime date { get; set; }
}

Related

Filter based on a string value in List<string> column in a table Entity Framework Core

I have a table with the following structure (code first approach using Entity Framework Core) in PostgreSQL
public class Product_Order
{
[Key]
public string product_number { get; set; }
public string customer_product_number { get; set; }
public List<string> product_statuses { get; set; }
public bool is_test { get; set; } = false;
public DateTime created_at { get; set; } = DateTime.UtcNow;
public DateTime updated_at { get; set; } = DateTime.UtcNow;
public string created_by { get; set; } = "system";
public string updated_by { get; set; } = "system";
}
Now, the product_statuses column usually contains of a list of statuses - ready, pickedup, scheduled, closed, cancelled.
I need to come up with a solution which returns me a list of product orders which DOES NOT CONTAIN orders which are closed or cancelled.
Here's the solution that I have at the moment which is not filtering as expected
_context.Product_Order.Where(t => t.is_test && !t.statuses.Contains("closed") && !t.statuses.Contains("cancelled")).ToList();
I think your code is ok for your data structure to find that information. I have created a dummy class and list to replicate your data and list. And I was able to find data by using you code. Sample Code given below what I have tested =>
void Test()
{
List<Product_Order> items = new List<Product_Order>();
var temp = new Product_Order() { product_number = "001", isTest = true };
temp.product_statuses = new List<string>();
temp.product_statuses.Add("good");
temp.product_statuses.Add("greate");
temp.product_statuses.Add("new");
items.Add(temp);
temp = new Product_Order() { product_number = "002", isTest = true };
temp.product_statuses = new List<string>();
temp.product_statuses.Add("good");
temp.product_statuses.Add("bad");
temp.product_statuses.Add("notnew");
items.Add(temp);
temp = new Product_Order() { product_number = "003", isTest = true };
temp.product_statuses = new List<string>();
temp.product_statuses.Add("n/a");
temp.product_statuses.Add("bad");
temp.product_statuses.Add("Closed");
items.Add(temp);
temp = new Product_Order() { product_number = "004", isTest = false };
temp.product_statuses = new List<string>();
temp.product_statuses.Add("n/a");
temp.product_statuses.Add("bad");
temp.product_statuses.Add("Cancelled");
items.Add(temp);
var finalOutput = items.Where(c => c.isTest == true && !c.product_statuses.Where(v => v.ToLower() == "closed").Any() && !c.product_statuses.Where(v => v.ToLower() == "cancelled").Any()).ToArray();
}
public class Product_Order
{
public string product_number { get; set; }
public bool isTest { get; set; }
public List<string> product_statuses { get; set; }
}
Finally , I think it is your data what not wright with you lambda expression. So, I modified for you a little bit.And that is
FINAL ANSWER:
var finalOutput = _context.Product_Order.Where(c => c.isTest == true && !c.product_statuses.Where(v => v.ToLower() == "closed").Any() && !c.product_statuses.Where(v => v.ToLower() == "cancelled").Any()).ToArray();
Please check my code and let me know.

Best way to remove elements from list field of list objects

I have the objects model the File and User:
public class File
{
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public uint Size { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<File> Files { get; set; }
}
I have created the list of objects of the model:
var users = new List<User> { new User
{
Id = 1,
Name = "Alex",
Files = new List<File>
{
new File
{
Id = 1,
Name = "123.txt",
Size = 20000
}
new File
{
Id = 3,
Name = "111.txt",
Size = 10
}
}
},
new User
{
Id = 2,
Name = "Andry",
Files = new List<File>
{
new File
{
Id = 1,
Name = "file.txt",
Size = 3
},
new File
{
Id = 2,
Name = "file.mp3",
Size = 4342
}
}
},
new User
{
Id = 3,
Name = "Jon",
Files = new List<File>
{
new File
{
Id = 1,
Name = "site.txt",
Size = 3324
}
}
},
};
The field with name - Files of object user list contains list of objects File.
How do remove files of users with Size <= 10 ?
This way is not working:
users.SelectMany(u => u.Files).ToList().RemoveAll(f => f.Size <= 10);
LINQ is a technology to query sources, not to modify them. So you should not use a query and change something in this query. Instead you can filter what you need to modify and then use a loop to actually apply the changes.
IEnumerable<User> usersWithSmallFiles = users
.Where(u => u.Files.Any(f => f.Size <= 10));
foreach (User u in usersWithSmallFiles)
u.Files = u.Files.Where(f => f.Size > 10).ToList();
You also can't use RemoveAll on IEnumerable<T>, it's a List<T>-method. That's why you have used ToList() in the query. But on this way you create a new list which is not related to your Files-list, it just contains the same files but the collection is different, so RemoveAll is pointless.

Converting objects with string[] properties to CSV or Excel

Is there any library out there that can serialize objects with array properties to .csv?
Let's say I have this model:
public class Product
{
public string ProductName { get; set; }
public int InStock { get; set; }
public double Price { get; set; }
...
public string[] AvailableVariants { get; set; }
}
Would something like that be possible to do?
Edit: I need to present some data in a csv/excel format. The thing is, I'm not sure if there is a simple way of achieving what I want with CSV serialization libraries or if I should rather focus on writing an Excel native file.
An example of result I'm looking for:
Product Name In Stock Price Variants
ABC 241 200 Normal
CAB 300 300 Normal
Red
Blue
CBA 125 100 Normal
White
Awesome
Red
ACB 606 75 Normal
Small
Large
X-Large
What would be the most efficient way to do this?
I'm not aware of any libraries that will do this, here's a console example of how I'd approach writing/reading from a CSV:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace TestingProduct
{
class TestingProduct
{
public class Product
{
public string ProductName { get; set; }
public int InStock { get; set; }
public double Price { get; set; }
public string[] AvailableVariants { get; set; }
public override string ToString() => $"{ProductName},{InStock},{Price}{(AvailableVariants?.Length > 0 ? "," + string.Join(",", AvailableVariants) : "")}";
public static Product Parse(string csvRow)
{
var fields = csvRow.Split(',');
return new Product
{
ProductName = fields[0],
InStock = Convert.ToInt32(fields[1]),
Price= Convert.ToDouble(fields[2]),
AvailableVariants = fields.Skip(3).ToArray()
};
}
}
static void Main()
{
var prod1 = new Product
{
ProductName = "test1",
InStock= 2,
Price = 3,
AvailableVariants = new string[]{ "variant1", "variant2" }
};
var filepath = #"C:\temp\test.csv";
File.WriteAllText(filepath, prod1.ToString());
var parsedRow = File.ReadAllText(filepath);
var parsedProduct = Product.Parse(parsedRow);
Console.WriteLine(parsedProduct);
var noVariants = new Product
{
ProductName = "noVariants",
InStock = 10,
Price = 10
};
var prod3 = new Product
{
ProductName = "test2",
InStock= 5,
Price = 5,
AvailableVariants = new string[] { "variant3", "variant4" }
};
var filepath2 = #"C:\temp\test2.csv";
var productList = new List<Product> { parsedProduct, prod3, noVariants };
File.WriteAllText(filepath2, string.Join("\r\n", productList.Select(x => x.ToString())));
var csvRows = File.ReadAllText(filepath2);
var newProductList = new List<Product>();
foreach (var csvRow in csvRows.Split(new string[] { "\r\n" }, StringSplitOptions.None))
{
newProductList.Add(Product.Parse(csvRow));
}
newProductList.ForEach(Console.WriteLine);
Console.ReadKey();
}
}
}
This code will work with a class that has a single object array property. Do you need something that can handle an object with multiple array properties?
I have written some kind of library to write csv files, have a look:
public static class CsvSerializer
{
public static bool Serialize<T>(string path, IList<T> data, string delimiter = ";")
{
var csvBuilder = new StringBuilder();
var dataType = typeof(T);
var properties = dataType.GetProperties()
.Where(prop => prop.GetCustomAttribute(typeof(CsvSerialize)) == null);
//write header
foreach (var property in properties)
{
csvBuilder.Append(property.Name);
if (property != properties.Last())
{
csvBuilder.Append(delimiter);
}
}
csvBuilder.Append("\n");
//data
foreach (var dataElement in data)
{
foreach (var property in properties)
{
csvBuilder.Append(property.GetValue(dataElement));
if (property != properties.Last())
{
csvBuilder.Append(delimiter);
}
}
csvBuilder.Append("\n");
}
File.WriteAllText(path, csvBuilder.ToString());
return true;
}
}
public class CsvSerialize : Attribute
{
}
Lets pretend you want to serialize following class:
public class MyDataClass
{
[CsvSerialize]
public string Item1 {get; set;}
[CsvSerialize]
public string Item2 {get; set;}
}
Then just do:
public void SerializeData(IList<MyDataClass> data)
{
CsvSerializer.Serialize("C:\\test.csv", data);
}
It takes a IList of your class and writes a csv.
It cant serialize arrays but that would be easy to implement.

C# Reactive Extensions (rx) FirstOrDefault enumerates entire collection

It seems that the expected behavior of FirstOrDefault is to complete after finding an item that matches the predicate and the expected behavior of concat is to evaluate lazily. However, the following example enumerates the entire collection even though the predicate matches the first item.
(Thanks for the friendlier code Shlomo)
void Main()
{
var entities = Observable.Defer(() => GetObservable().Concat());
Entity result = null;
var first = entities.FirstOrDefaultAsync(i => i.RowId == 1).Subscribe(i => result = i);
result.Dump();
buildCalled.Dump();
}
// Define other methods and classes here
public IEnumerable<IObservable<Entity>> GetObservable()
{
var rows = new List<EntityTableRow>
{
new EntityTableRow { Id = 1, StringVal = "One"},
new EntityTableRow { Id = 2, StringVal = "Two"},
};
return rows.Select(i => Observable.Return(BuildEntity(i)));
}
public int buildCalled = 0;
public Entity BuildEntity(EntityTableRow entityRow)
{
buildCalled++;
return new Entity { RowId = entityRow.Id, StringVal = entityRow.StringVal };
}
public class Entity
{
public int RowId { get; set; }
public string StringVal { get; set; }
}
public class EntityTableRow
{
public int Id { get; set; }
public string StringVal { get; set; }
}
Is this the expected behavior? Is there a way to defer the enumeration of the objects (specifically the building in this case) until truly needed?
The following is Linqpad-friendly code equivalent to what you have:
void Main()
{
var entities = Observable.Defer(() => GetObservable().Concat());
Entity result = null;
var first = entities.FirstOrDefaultAsync(i => i.RowId == 1).Subscribe(i => result = i);
result.Dump();
buildCalled.Dump();
}
// Define other methods and classes here
public IEnumerable<IObservable<Entity>> GetObservable()
{
var rows = new List<EntityTableRow>
{
new EntityTableRow { Id = 1, StringVal = "One"},
new EntityTableRow { Id = 2, StringVal = "Two"},
};
return rows.Select(i => Observable.Return(BuildEntity(i)));
}
public int buildCalled = 0;
public Entity BuildEntity(EntityTableRow entityRow)
{
buildCalled++;
return new Entity { RowId = entityRow.Id, StringVal = entityRow.StringVal };
}
public class Entity
{
public int RowId { get; set; }
public string StringVal { get; set; }
}
public class EntityTableRow
{
public int Id { get; set; }
public string StringVal { get; set; }
}
If you change GetObservable to the following, you'll get the desired result:
public IObservable<IObservable<Entity>> GetObservable()
{
var rows = new List<EntityTableRow>
{
new EntityTableRow { Id = 1, StringVal = "One"},
new EntityTableRow { Id = 2, StringVal = "Two"},
};
return rows.ToObservable().Select(i => Observable.Return(BuildEntity(i)));
}
It appears the implementation of Concat<TSource>(IEnumerable<IObservable<TSource>>) is eager in evaluating the enumerable, whereas the implementation of Concat<TSource>(IObservable<IObservable<TSource>>) and ToObservable<TSource>(IEnumerable<TSource>) maintain laziness appropriately. I can't say I know why.

Converting an array to object

I have 2 types of string: Mer and Spl
// Example
string testMer = "321|READY|MER";
string testSpl = "321|READY|SPL";
Then I will split them:
var splitMer = testMer.Split('|');
var splitSpl = testSpl.Split('|');
I have an object to save them
public class TestObject
{
public int id { get; set; }
public string status { get; set; }
public string type { get; set; }
}
Question: How to convert the Array into the TestObject?
var converted = new TestObject
{
id = int.Parse(splitMer[0]),
status = splitMer[1],
type = splitMer[2]
};
You will need to add some error checking.
var values = new List<string> { "321|READY|MER", "321|READY|SPL" };
var result = values.Select(x =>
{
var parts = x.Split(new [] {'|' },StringSplitOptions.RemoveEmptyEntries);
return new TestObject
{
id = Convert.ToInt32(parts[0]),
status = parts[1],
type = parts[2]
};
}).ToArray();
You just need to use object initializers and set your properties.By the way instead of storing each value into seperate variables, use a List.Then you can get your result with LINQ easily.
var splitMer = testMer.Split('|');
var testObj = new TestObject();
testObj.Id = Int32.Parse(splitMer[0]);
testObj.Status = splitMer[1];
testObj.type = splitMer[2];
How about adding a Constructor to your Class that takes a String as a Parameter. Something like this.
public class TestObject
{
public int id { get; set; }
public string status { get; set; }
public string type { get; set; }
public TestObject(string value)
{
var valueSplit = value.Split('|');
id = int.Parse(valueSplit[0]);
status = valueSplit[1];
type = valueSplit[2];
}
}
Usage:
TestObject tst1 = new TestObject(testMer);
TestObject tst2 = new TestObject(testSpl);

Categories

Resources