Group records stored in one-dimensional array using LINQ - c#

I have a situation, where I get data from the database in such a way, that everything is stored in one-dimensional array.
For example:
User table: UserId, Name
Group table: GroupId, Name
UserGroup table: UserId, GroupId
As a result of joining these tables I obtain array of the following form:
result[0] = "1" // user id
result[1] = "John Doe" // user name
result[2] = "121" // group id
result[3] = "SomeGroup" // group name
result[4] = "1" // user id
result[5] = "John Doe" // user name
result[6] = "2135" // group id
result[7] = "SomeOtherGroup" // group name
I know it's not a good solution of keeping data, but these data are coming to me from some other piece of code which I am not allowed to change, so I have to deal with it.
My questions are:
Is this possible to use LINQ in order to parse this array and place data in my own objects (User class with Groups collection in it).
What is other best way to handle it if not by LINQ?

Pure linq Expression :
int i = 0;
var objects = result.GroupBy(x => Math.Floor(i++ / 4.0))
.Select(g => new { id =g.ElementAt(0), name = g.ElementAt(1), gId= g.ElementAt(2), group = g.ElementAt(3)})
.GroupBy(x=>new {x.id, x.name}, x=>new {x.gId, x.group})
.Select(y=>new {y.Key, groups = y.ToList()});
In the first GroupBy I group results in 4 elements subsets using a floor and a temporary variable.
Then The next Select put the resulting arrays in an anonymous type for better usability in the next steps.
The next GroupBy is used to group the entries by Employee. The Key will be the employee and the values will be the corresponding Groups.
Finaly the lase Selectis used to put the GroupByresult in a better shape. I choose to put the result in an other anonymous type but You could instantiate you custom objects here and put the values in the right fields using curly brace constructor.

If your logic depends on indexes LINQ is is rarely the right tool. It results in less readable, maintainable, efficient and robust code than with plain loops.
I would use something like following to create two dictionaries representing the many to many relation. Note the for-loop which increments by 4 on every iteration since that seems to be the user-group-"package":
var userIdGroups = new Dictionary<int, HashSet<Group>>();
var groupIdUsers = new Dictionary<int, HashSet<User>>();
for(int i = 0; i < result.Length; i += 4)
{
int id;
if(int.TryParse(result[i], out id))
{
string name = result.ElementAtOrDefault(i + 1);
if(name == null)
continue; // end, invalid data
User user = new User{ UserId = id, Name = name };
string groupID = result.ElementAtOrDefault(i + 2);
if(!int.TryParse(groupID, out id))
continue; // end, invalid data
name = result.ElementAtOrDefault(i + 3);
if(name == null)
continue; // end, invalid data
Group group = new Group{ GroupId = id, Name = name };
HashSet<Group> userGroups;
HashSet<User> groupUsers;
if (userIdGroups.TryGetValue(user.UserId, out userGroups))
userGroups.Add(group);
else
userIdGroups.Add(user.UserId, new HashSet<Group>{ group });
if (groupIdUsers.TryGetValue(group.GroupId, out groupUsers))
groupUsers.Add(user);
else
groupIdUsers.Add(group.GroupId, new HashSet<User> { user });
}
}
The result is:
the user-dictionary contains one user with two groups
the group-dictionary contains two groups which map to the same user
You have to override Equals and GetHashCode to compare the ID's:
class User
{
public int UserId { get; set; }
public string Name { get; set; }
public override bool Equals(object obj)
{
User u2 = obj as User;
if (u2 == null) return false;
return UserId == u2.UserId;
}
public override int GetHashCode()
{
return UserId;
}
}
class Group
{
public int GroupId { get; set; }
public string Name { get; set; }
public override bool Equals(object obj)
{
Group g2 = obj as Group;
if (g2 == null) return false;
return GroupId == g2.GroupId;
}
public override int GetHashCode()
{
return GroupId;
}
}

You can do it with the basic structures like loops:
void Main()
{
var result = new string[] {"1","John Doe","2","Joes Group","3","Jack Daniel","4","Jacks Group","5","Foo Bar","6","FooBar Group",};
List<Person> personList = new List<Person>();
List<Group> groupList = new List<Group>();
for(int i = 0; i < result.Length; i+=2)
{
i = i + 2;
//check if group does not already exist
groupList.Add(new Group() {Name = result[i+1]});
}
for(int i = 0; i < result.Length; i+=2)
{
//check if person exists.
//if person only add group to person personList.Where(x => x.Name ==result[i+1])....
personList.Add(new Person() { Id = int.Parse(result[i]),
Name = result[i+1],
Groups = new List<Group>()
{
groupList.FirstOrDefault (l => l.Name == result[i+3])
}
});
i = i+2;
}
personList.Dump();
}
public class Person
{
public Person()
{
Groups = new List<Group>();
}
public int Id { get; set; }
public string Name { get; set; }
public List<Group> Groups { get; set; }
}
public class Group
{
public string Name { get; set; }
}
// Define other methods and classes here
Output:
Please take advise: this code does not contain any validation logic, or duplicate checks. You'll have to imlpement this by yourself.
But before you implement something like this, I'd rather change the way you get your data delivered. this way you would deal with the root of your peroblems not with the symptoms.

i think no need to linq
//some class
public class Result
{
public string UserId {get;set;}
public string UserName {get;set;}
public string GroupId {get;set;}
public string GroupName {get;set;}
public string UserGroupUserId {get;set;}
public string UserGroupUserName {get;set;}
public string UserGroupId {get;set;}
public string UserGroupGroupId {get;set;}
}
// your array
private void Form1_Load(object sender, EventArgs e)
{
string[] result = new string[8];
result[0] = "1";
result[1] = "John Doe";
result[2] = "121";
result[3] = "SomeGroup";
result[4] = "1";
result[5] = "John Doe";
result[6] = "2135";
result[7] = "SomeOtherGroup";
Result r = CastResult(result);
}
// simple cast array to some type
public Result CastResult(string[] array)
{
return new Result() { UserId=array[0], UserName=array[1], GroupId=array[2], GroupName=array[3], UserGroupUserId=array[4], UserGroupUserName=array[5] , UserGroupId=array[6], UserGroupGroupId=array[7] };
}

Related

changing the sorted column value change the sorting list , while wants to use the ThenByDescending on first ordered list

I have following code, In which there are list of students , and I want to sort the students first by value column which contains decimal values and after that I want to sort the already sorted list with same column but with different values . Just for understanding , I changed values using foreach loop in the below example.
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var students = new List<Student>()
{
new Student() { StudentId=1,Name = "Alice", Appd = 10, Value = 3.5 },
new Student() { StudentId=2,Name = "Bob", Appd = 10, Value = 3.7 },
new Student() { StudentId=3,Name = "Raul", Appd = 10, Value = 0.1 },
new Student() { StudentId=4,Name = "Charlie", Appd = 0, Value = 3.6 },
new Student() { StudentId=5,Name = "Dave", Appd = 0, Value = 3.9 },
new Student() { StudentId=6,Name = "Emma", Appd = 0, Value = 3.8 }
};
var orderedFields = students.OrderByDescending(x => x.Value);//5,6,2,4,1,3
foreach ( Student s in orderedFields )
{
s.Value = 120;
}
orderedFields = orderedFields.ThenByDescending(x => x.Value);
var newlist1 = orderedFields.Select(X => X.StudentId).ToList();
}
}
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public int Appd { get; set; }
public double Value { get; set; }
}
}
but as soon I change the Value column values it start to change the order of items in list , and if I take this in another list then I will be not able to use the ThenByDescending feature results.
This is sample code to simplify the problem , in real example these columns name come from Database and based on those columns I want to sort the list, first by first column and then by another columns mentioned. For example in MySQL it will be something like this order by col1 desc, col2 desc.
As everybody is comments is discussing the clone and then sort again the list . so here is issue with that approach.
#1. First Set sorting values in Value column for each student :
Value column first contains for each student either 1 or 0 depending on its enrollment date from the cut off date.
#2 Then on same Value column there is CGPA for each student so student should be sorted based on that.
In short all students who apply before cut off date should appear
first and then sort by descending CGPA and then students who apply
after cut off date but those also should come in descending order of
CGPA.
problem is I have only one column here for values, on which need to be sort.
Second edit :
if (_trackConfigManager.RankDependentOnFields.Any())
{
infoFields.ForEach(x => x.PropertyToCompare = _trackConfigManager.RankDependentOnFields.FirstOrDefault().FieldId);
//Order the table withrespect to the firstfield
var orderedFields = infoFields.OrderByDescending(x => x.Value);
//Then skip the first element and order the rest of the fields by descending.
foreach (var field in __trackConfigManager.RankDependentOnFields.RemoveFirst())
{
infoFields.ForEach(x => x.PropertyToCompare = field.FieldId);
orderedFields = orderedFields.ThenByDescending(x => x.Value);
}
//Format a studentId, Rank dictionary from the above orderded table
int rank = 1 + GetMaxRank(programId, statusId);
}
and RankAggregate class as follow :
public class RankAggregate
{
public student_highschool_info HsInfoObj { get; set; }
public student_interview_info IntInfoObj { get; set; }
public student StuObj { get; set; }
private student_program SpObj { get; set; }
public string PropertyToCompare { get; set; }
public bool IsDateTimeField { get; set; }
public long StudentId { get; set; }
public int Choice { get; set; }
public double Value
{
get
{
var tokens = PropertyToCompare.Split(new char[] {':'});
if (tokens.Count() > 1)
{
PropertyToCompare = (Choice == 1)
? "student_interview_FirstPrgTotalScore"
: (Choice == 2) ? "student_interview_SecondPrgTotalScore" : "dummy";
}
var fldInfo = ReflectionUtility.GetPublicPropertyName(typeof(student_highschool_info), PropertyToCompare);
if (fldInfo != null)
{
if (HsInfoObj == null)
return 0;
IsDateTimeField = (fldInfo.PropertyType == typeof(DateTime?));
if (IsDateTimeField)
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student_highschool_info),
PropertyToCompare, HsInfoObj) ?? 0;
var dt = DateTime.Parse(val1.ToString());
return -Convert.ToDouble(dt.Ticks);
}
else
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student_highschool_info),
PropertyToCompare, HsInfoObj) ?? 0;
return Convert.ToDouble(val1);
}
}
fldInfo = ReflectionUtility.GetPublicPropertyName(typeof(student_interview_info), PropertyToCompare);
if (fldInfo != null)
{
if (IntInfoObj == null)
return 0;
IsDateTimeField = (fldInfo.PropertyType == typeof(DateTime?));
if (IsDateTimeField)
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student_interview_info),
PropertyToCompare, IntInfoObj) ?? 0;
var dt = DateTime.Parse(val1.ToString());
return -Convert.ToDouble(dt.Ticks);
}
else
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student_interview_info),
PropertyToCompare, this.IntInfoObj) ?? 0;
return Convert.ToDouble(val1);
}
}
fldInfo = ReflectionUtility.GetPublicPropertyName(typeof(student), PropertyToCompare);
if (fldInfo != null)
{
if (StuObj == null)
return 0;
IsDateTimeField = (fldInfo.PropertyType == typeof(DateTime?));
if (IsDateTimeField)
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student),
PropertyToCompare, StuObj) ?? 0;
var dt = DateTime.Parse(val1.ToString());
return -Convert.ToDouble(dt.Ticks);
}
else
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student),
PropertyToCompare, this.StuObj) ?? 0;
return Convert.ToDouble(val1);
}
}
return 0.0;
}
}
public RankAggregate(long studentId, student_highschool_info _hsInfo, student_interview_info _intInfo, student _profileInfo, student_program _spInfo)
{
StudentId = studentId;
HsInfoObj = _hsInfo;
IntInfoObj = _intInfo;
StuObj = _profileInfo;
SpObj = _spInfo;
if (SpObj != null)
{
Choice = SpObj.choice;
}
}
}
Don't know why can't you add another field to the Student class, anyway since you can't do that, you have to fix these values in some places, for example using a tuple:
var studentsWithValues = students.Select(s => (s, s.Value))
.ToList();
Then after changing the values, you can sort the above array:
var orderedFields = studentsWithValues.OrderByDescending(t => t.Value)
.ThenByDescending(t => t.s.Value)
.Select(t => t.s)
Update for uncertain columns
Bind each student object with a list of values:
var studentsWithValues = students.Select(s => new
{
Student = s,
Values = new List<double> { s.Value }
})
.ToList();
After the values are updated, append each value to the binded list:
UpdateValues();
studentsWithValues.ForEach(t => t.Values.Add(t.Student.Value));
Then you can sort these values:
var e = studentsWithValues.OrderByDescending(t => t.Values[0]);
var valueCount = studentsWithValues.First().Values.Count;
for (int i = 1; i < valueCount; i++)
{
int j = i;
e = e.ThenByDescending(t => t.Values[j]);
}
var orderedFields = e.Select(t => t.Student);
Short answer
Use:
var orderedStudents = students
.OrderByDescending(student => student.Value)
.ToList();
foreach (Student student in orderedStudents) etc.
Longer answer
Your orderedFields is not a list, nor a sequence. It is an object that can be enumerated. It has not been enumerated yet. In other words: it is not a Collection<T>, it is an IEnumerable<T>. Usually in descriptions you'll find the phrases: delayed execution or deferred execution.
When you execute foreach (Student s in orderedFields), you start to enumerate the items in students. You don't enumerate the items in the original order, you enumerate them ordered by .Value.
but as soon I change the Value column values it start to change the order of items in list
So, the next time you enumerate orderedFields, the items in students are enumerated again, and ordered again by the changed value of .Value.
If you want to change the source of the items in your LINQ statement, you have to execute the delayed execution by calling one of the LINQ methods that doesn't return IEnumerable<T>, like ToList(), ToArray(), ToDictionary(), but also FirstOrDefault(), Sum(), Count(), Any()
By calling one of the non-delayed methods, the source is enumerated and the result is put in a new object. If you change the items in the new object, and use this new object as source for your next LINQ-statement, then the order of the new object is used, not the order in the original object.
Careful: if you put the references of the original items in the new List, and you change the values, you change the original items. If you don't want that, use a Select(student => new {...}) to put the values in a new object. If you change those values, the original students are not affected.

How to retrieve data from dynamic table in mvc

I'm writing new api call for my android app, I want to retrieve data from dynamic table, I mean when user select "A" button from Andriod I want to retrieve data from "A" table and if "B" is click , retrieve data from "B" table. Someone help me to find the possible solution.
I want to replace with variable for "JBCTNRITEMs" from entities.JBCTNRITEMs (table-name)
var query = (from jbct in entities.JBCTNRITEMs
where jbid.Contains(jbct.jbid.ToString()) && boxid.Contains(jbct.TrackingNo.ToString())
select jbct).ToArray();
int id = 0;
if (query.Length > 0)
{
id = (query[0].id);
}
return id;
Here are the two different possibilities as an example
if (module.ToLower() == "module-a")
{
var imageinfo = (from jb in entities.TableA.AsEnumerable()
where scanID.Contains(jb.aid.ToString())
select jb).ToArray();
InsertGENIMAGE(userID, scanID, FilePath, imageinfo, "AIMAGE");
}
else if (module.ToLower() == "module-b")
{
var imageinfo = (from jb in entities.TableB.AsEnumerable()
where scanID.Contains(jb.bid.ToString())
select jb).ToArray();
InsertGENIMAGE(userID, scanID, FilePath, imageinfo, "BIMAGE");
}
Here, query stores what you are you trying to select. As long as you are trying to select same type or same anonymous type, it will work.
Here is a simple example:
class Test1
{
public int ID { get; set; }
public string Name { get; set; }
}
class Test2
{
public int ID { get; set; }
public string Name { get; set; }
}
var test1Lst = new List<Test1>
{
new Test1() { ID = 1, Name = "Jitendra" },
new Test1() { ID = 2, Name = "Jahnavi" }
};
var test2Lst = new List<Test2>
{
new Test2() { ID = 1, Name = "Aaditri" },
new Test2() { ID = 2, Name = "Pankaj" }
};
var test = false;
var query = test ? (from t in test1Lst select new { ID = t.ID, Name = t.Name }) : (from t in test2Lst select new { ID = t.ID, Name = t.Name });
// Put whatever condition you want to put here
query = query.Where(x => x.ID == 1);
foreach(var t1 in query)
{
Console.WriteLine(t1.ID + " " + t1.Name);
}
I guess in this case I would suggest to use a generic method :
private T GetMeTheFirstEntry<T>(Expression<Func<T, bool>> filter) where T : class
{
return entities.GetTable<T>().FirstOrDefault(filter);
}
The GetTable will allow you to interchange the tableA and tableB. You would call it the following way:
TableA tableA_entity = GetMeTheFirstEntry<TableA>(jb => scanID.Contains(jb.aid.ToString()));
TableB tableB_entity = GetMeTheFirstEntry<TableB>(jb => scanID.Contains(jb.bid.ToString()));
If the filtering was successfull, then the retrieved object will not be null and you can use it:
int a_id = tableA_entity.aid;

Updating Hierarchical Numbering

I have below classes in my application which holds hierarchical data as below:
Class A
{
string Id{get;set;}
string Name{get;set;}
string Order{get;set;}
string ParentId{get;set;}
}
Class B
{
string Id{get;set;}
string Name{get;set;}
string Order{get;set;}
string ClassAId{get;set;}
}
Class C
{
string Id{get;set;}
string Name{get;set;}
string Order{get;set;}
string ClassBId{get;set;}
}
Sample data for class A would be
Id = "1.1" Id = "2.1"
Name = "SomeName" Name = "Name2"
Order = 1 Order = 1
ParentId = "1" ParentId = 2
Id = "1.2" Id = "2.2"
Name = "Name2" Name = "Name3"
Order = 2 Order = 2
ParentId = "1" ParentId = 2
Sample data for Class B would be
Id = "1.1.1" Id = "2.1.1"
Name = "SomeName" Name = "Name2"
Order = 1 Order = 1
ParentId = "1.1" ParentId = 2.1
Id = "1.2.1" Id = "2.1.2"
Name = "Name2" Name = "Name3"
Order = 2 Order = 2
ParentId = "1.2" ParentId = 2.1
Similarly for Class C, data would look like
Id = "1.1.1.1" Id = "2.1.1.1"
Name = "SomeName" Name = "Name2"
Order = 1 Order = 1
ParentId = "1.1.1" ParentId = 2.1.1
Now if user wants to update a value in between the existing values, the way it should work is, suppose I am entering a value between 1.2 and 1.3 for Class A, it should first create a new value called 1.4, then move all the contents of 1.3 and its children to 1.4 (i.e, if 1.3 has its own children like 1.3.1 so on and 1.3.1.1 so on all should be renamed to 1.4.1 and 1.4.1.1 respectively and 1.3 should be free of any hierarchy with empty value. Basically in between inserts should update the complete hierarchy of a record. I am able to generate next sequence correctly by finding current Id and finding maximum of order and adding 1 to it. Issue I am facing is in between inserts and updating whole hierarchy.
Any ideas would help. Below is the code I have written for entering a new value Class A :
//Find the max order, increment it by 1 and insert the new record.
var currentClass = listOfClassA.Where(x => x.Id = currentId).SingleOrDefault();
var maxOrder = listOfClassA.Max(x => x.Order);
var objClassA = new A();
objClassA.Order = maxOrder + 1;
objClassA.ParentId = currentClassA.ParentId;
objClassA.Name = "";
objClassA.Id = currentClassA.ParentId + "." + objClassA.Order;
Just an idea, as on your request, but wouldn't it be easier to include the classes into your hierarchical data structure, e.g.:
class A
{
public string Id{get;set;}
public string Name{get;set;}
public string Order{get;set;}
//this is the tricky one.
public string ParentId{get;set;}
}
class B
{
public string Id{get;set;}
public string Name{get;set;}
public string Order{get;set;}
public A ClassA{get;set;}
}
class C
{
public string Id{get;set;}
public string Name{get;set;}
public string Order{get;set;}
public B ClassB{get;set;}
}
Another idea: since your classes are very similar, you could also create a true hierarchical structure like this:
public class Foo
{
public string Id{get;set;}
public string Name{get;set;}
public string Order{get;set;}
public Foo Parent {get;set;}
public Foo Child{get;set;}
}
Expanding the previous idea, you'll find that you have created a sort of LinkedList, e.g.:
public class Foo
{
public string Id{get;set;}
public string Name{get;set;}
public string Order{get;set;}
}
var list = new LinkedList<Foo>();
Yet another option: which is a more tree like structure, I believe it's called the composite pattern:
public class Foo
{
public string Id{get;set;}
public string Name{get;set;}
public string Order{get;set;}
public Foo Parent {get;set;}
public IEnumerable<Foo> Children{get;set;}
}
I hope it helps. Using one of the above patterns will make it easy to produce the hierarchical ID string, like 1.2.3.4.e.t.c., if you even need it; because it's also very easy to serialize the whole thing. Beware of circular references though ;-)
Also the other operations, to find maxima or minima are quite doable, using SelectMany or recursive calls.
We decided to change our design to use dynamic expando objects and use them to generate the hierarchical numbering [Our requirements were to use dynamic collections (mongodb) and generate them on the fly]. Although the class structure will remain same as above. Below is the code we wrote for hierarchical numbering. This code takes the Id as input( like 1.1 or 1.2 or 1.1.1 or 1.1.1.1)
dynamic expando = new ExpandoObject();
var collectionModel = expando as IDictionary<string, Object>;
var lastDotPosition = value.LastIndexOf('.');
var parentId = value.Substring(0, lastDotPosition);
collectionModel.Add("ParentId", parentId);
var order = Convert.ToInt32(value.Split('.').Last());
var newOrder = order + 1;
var Id = parentId + '.' + newOrder.ToString();
collectionModel.Add("Index", newOrder);//newOrder
collectionModel["Id"] = Id;
var filter = Builders<dynamic>.Filter.Gt("Index", order);
filter = filter & Builders<dynamic>.Filter.Eq("ParentId", parentId);
var collection = _db.GetCollection<dynamic>(collectionName);
var remainingList = collection.Find(filter).ToList();
var dynamicList = new List<ExpandoObject>();
dynamicList.Add((ExpandoObject)collectionModel);
// below updates the next record ids and parent id to ensure numbering is maintained
for (int i = 0; i < remainingList.Count; i++)
{
var remainingRecord = remainingList[i] as IDictionary<string, object>;
var newRecord = new ExpandoObject() as IDictionary<string, Object>;
for(int j = 0; j < listOfFieldNames.Count; j++)
{
var fieldName = listOfFieldNames[j];
Object dictValue = "";
remainingRecord.TryGetValue(fieldName, out dictValue);
if (fieldName == "Id")
{
newRecord[fieldName] = parentId + "." + (newOrder + 1);
}
else
{
newRecord[fieldName] = dictValue;
}
}
newRecord["Index"] = newOrder + 1;
newRecord["ParentId"] = parentId;
newOrder++;
dynamicList.Add((ExpandoObject)newRecord);
}
//Now update child or grandchild if any
var updateForChildList = remainingList.OrderByDescending(x => ((IDictionary<string, object>)x)["Index"]).ToList();
for (int k = 0; k < updateForChildList.Count; k++)
{
var oldRecord = updateForChildList[k] as IDictionary<string, object>;
var oldParentId = oldRecord["Id"];
Object dictValue = "";
oldRecord.TryGetValue("Index", out dictValue);
var newParentId = oldRecord["ParentId"] + "." + Convert.ToString(Convert.ToInt32(dictValue.ToString()) + 1);
UpdateParentIdForChildren(oldParentId.ToString(), newParentId, Convert.ToInt32(collectionOrder + 1));
}
collection.DeleteMany(filter);
collection.InsertMany(dynamicList);
Use recursion to find children and grand children and update their parentids and ids
public void UpdateParentIdForChildren(string oldParentId, string newParentId, int collectionIndex)
{
if (collectionIndex > collectionList.Count)
{
return;
}
var currentCollection = _db.GetCollection<dynamic>(collectionName);
var filter = Builders<dynamic>.Filter.Eq("ParentId", oldParentId);
var oldParentIdList = currentCollection.Find(filter).ToList();
var reoldParentIdList = oldParentIdList.OrderByDescending(x => ((IDictionary<string, object>)x)["Index"]).ToList();
if (reoldParentIdList.Count > 0)
{
for (int i = 0; i < reoldParentIdList.Count; i++)
{
var remainingRecord = reoldParentIdList[i] as IDictionary<string, object>;
Object OldIdValue = "";
remainingRecord.TryGetValue("Id", out OldIdValue);
Object indexValue = "";
remainingRecord.TryGetValue("Index", out indexValue);
var newId = newParentId + '.' + indexValue;
currentCollection.UpdateOne(filter, Builders<dynamic>.Update.Set("Id", newId));
currentCollection.UpdateOne(filter, Builders<dynamic>.Update.Set("ParentId", newParentId));
UpdateParentIdForChildren(OldIdValue.ToString(), newId, collectionIndex + 1);
}
}
}

How do you use linq to xml to find matching nodes in two different xml files

I just asked another question here and the answer was spot on.
But that addressed what was essentially a syntax problem. Now I need help with an actual resolution.
This is the same code from the previou question (fixed up and with stuff added).
XElement FILE1 = XElement.Load (#"..\FILE1.XML");
XElement FILE2 = XElement.Load (#"..\FILE2.XML");
var orders = from file1 in FILE1.Descendants("Players").Elements("Player")
select new {
name=new {
clientID=ulm.Element("ClientID").Value,
firstName=file1.Element("FirstName").Value,
lastName=file1.Element("LastName").Value
}
};
var orders2 =
from file2 in FILE2.Descendants("Players").Elements("Player")
select new {
name=new {
clientID=ulm.Element("ClientID").Value,
firstName=file2.Element("FirstName").Value,
lastName=file2.Element("LastName").Value
}
};
var matchingResults = from i in orders from j in orders2 where (i.name.firstName==j.name.firstName && i.name.lastName==j.name.lastName)
select i;
matchingResults.Dump()
To make it interesting I have added a ClientID to each sequence result before trying to match them up.
What I need is to know does a Player node from order EXISTS in a player node from orders2. Or does it NOT EXIST? Ideally I would also be able to CHOOSE the selection criteria for the NOT EXISTS/EXISTS check. (LastName, or FirstName && LastName, or ClientID only, etc.)
I have NO IDEA how to go about this. Thanks for your help.
Use Enumerable.Intersect or Enumerable.Except
Use concrete types instead of anonymous types.
To be able to choose the selection criteria you can create a parameterized IEqualityComparer<Client>.
class Client
{
public string ClientID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
[Flags]
enum Criteria {
ClientID, FirstName, LastName
}
class ClientEqualityComparer : IEqualityComparer<Client> {
private Criteria criteria;
public ClientEqualityComparer(Criteria criteria) {
this.criteria = criteria;
}
#region IEqualityComparer<Client> Membres
public bool Equals(Client x, Client y)
{
if (criteria.HasFlag(Criteria.ClientID) && x.ClientID != y.ClientID)
return false;
if (criteria.HasFlag(Criteria.FirstName) && x.FirstName != y.FirstName)
return false;
if (criteria.HasFlag(Criteria.LastName) && x.LastName != y.LastName)
return false;
return true;
}
public int GetHashCode(Client obj)
{
int hash = 17;
if (criteria.HasFlag(Criteria.ClientID))
hash = hash * 31 + obj.ClientID;
if (criteria.HasFlag(Criteria.FirstName))
hash = hash * 31 + obj.FirstName;
if (criteria.HasFlag(Criteria.LastName))
hash = hash * 31 + obj.LastName;
}
#endregion
}
static void Main(string[] args)
{
IEnumerable<Client> orders;
IEnumerable<Client> orders2;
//...
var matchingIdFn = orders.Intersect(orders2,
new ClientEqualityComparer(Criteria.ClientID | Criteria.FirstName));
var matchingIdFnLn = orders.Intersect(orders2,
new ClientEqualityComparer(Criteria.ClientID | Criteria.FirstName | Criteria.LastName));
var different = orders.Except(orders2, new ClientEqualityComparer(Criteria.ClientID | Criteria.FirstName));
}
var orders = from file1 in FILE1.Descendants("Players").Elements("Player")
select new Client{
ClientID=ulm.Element("ClientID").Value,
FirstName=file1.Element("FirstName").Value,
LastName=file1.Element("LastName").Value
};
I second Ahmed KRAIEM's suggestion to use Intersect or Except
Here's another solution that lets you compare using any arbitrary lambda:
void Main()
{
XElement FILE1 = XElement.Parse(
#"<Root>
<Players>
<Player><ClientId>1</ClientId><FirstName>Bob</FirstName><LastName>Smith</LastName></Player>
<Player><ClientId>2</ClientId><FirstName>John</FirstName><LastName>Smith</LastName></Player>
</Players>
</Root>");
XElement FILE2 = XElement.Parse(
#"<Root>
<Players>
<Player><ClientId>2</ClientId><FirstName>Bob</FirstName><LastName>Smith</LastName></Player>
<Player><ClientId>3</ClientId><FirstName>Mike</FirstName><LastName>Smith</LastName></Player>
</Players>
</Root>");
var orders = from file1 in FILE1.Descendants("Players").Elements("Player")
select new Player(Int32.Parse(file1.Element("ClientId").Value), file1.Element("FirstName").Value, file1.Element("LastName").Value);
var orders2 = from file2 in FILE2.Descendants("Players").Elements("Player")
select new Player(Int32.Parse(file2.Element("ClientId").Value), file2.Element("FirstName").Value, file2.Element("LastName").Value);
//orders.Dump();
//orders2.Dump();
var exists = orders2.Intersect(orders, new LambdaEqualityComparer<Player>((i, j) => i.FirstName == j.FirstName && i.LastName == j.LastName));
// or
//var exists = orders2.Intersect(orders, new LambdaEqualityComparer<Player>((i, j) => i.ClientId == j.ClientId));
exists.Dump();
var notExists = orders2.Except(orders, new LambdaEqualityComparer<Player>((i, j) => i.FirstName == j.FirstName && i.LastName == j.LastName));
// or
//var notExists = orders2.Except(orders, new LambdaEqualityComparer<Player>((i, j) => i.ClientId == j.ClientId));
notExists.Dump();
}
public class Player
{
public int ClientId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Player(int clientId, string firstName, string lastName)
{
ClientId = clientId;
FirstName = firstName;
LastName = lastName;
}
}
public class LambdaEqualityComparer<T> : IEqualityComparer<T>
{
private Func<T, T, bool> EqualityComparer { get; set; }
public LambdaEqualityComparer(Func<T, T, bool> equalityComparer)
{
EqualityComparer = equalityComparer;
}
public bool Equals(T x, T y)
{
return EqualityComparer(x, y);
}
public int GetHashCode(T obj)
{
// If the hash codes are different, then Equals never gets called. Make sure Equals is always called by making sure the hash codes are always the same.
// (Underneath, the .NET code is using a set and the not (!) of a Find method to determine if the set doesn't already contain the item and should be added.
// Find is not bothering to call Equals unless it finds a hash code that matches.)
//return obj.GetHashCode();
return 0;
}
}
Note that if you don't want to create a Player object, you can remove it completely, populate your anonymous objects in orders and orders2 just like you were doing before, and create a new LambdaEqualityComparer<dynamic> instead of new LambdaEqualityComparer<Player>, though it will be slower due to reflection calls on dynamic.

Get the differences between 2 lists

I have two lists (ListA and ListB), the type of these lists is the same PersonInfo, the Loginfield is a unique key.
public class PersonInfo
{
public string Login { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public bool Active { get; set; }
}
I'd like compare these two lists :
I'd like get a list of items in ListA not available in ListB
For the items available in both lists, I'd like get a list from ListA (Login field is a unique key) for the item where there is a difference between the two lists.
Example : if for the Login "MyLogin" in ListA, the value of FirstName does not match with the value in ListB. The item with "MyLogin" as Login must be part of the result list.
Example : if the Age between ListA and ListB for a specific login is different, the item must be part of the result
Thanks.
To compare objects of custom data type lists, you will need to implement IEquatable in your class and override GetHashCode()
Check this MSDN Link
Your class
public class PersonInfo : IEquatable<PersonInfo>
{
public string Login { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public bool Active { get; set; }
public bool Equals(PersonInfo other)
{
//Check whether the compared object is null.
if (Object.ReferenceEquals(other, null)) return false;
//Check whether the compared object references the same data.
if (Object.ReferenceEquals(this, other)) return true;
//Check whether the properties are equal.
return Login.Equals(other.Login) && FirstName.Equals(other.FirstName) && LastName.Equals(other.LastName) && Age.Equals(other.Age) && Active.Equals(other.Active);
}
public override int GetHashCode()
{
int hashLogin = Login == null ? 0 : Login.GetHashCode();
int hashFirstName = FirstName == null ? 0 : FirstName.GetHashCode();
int hashLastName = LastName == null ? 0 : LastName.GetHashCode();
int hashAge = Age.GetHashCode();
int hashActive = Active.GetHashCode();
//Calculate the hash code.
return (hashLogin + hashFirstName + hashLastName) ^ (hashAge + hashActive);
}
}
Then here is how you use it (as listed in Pranay's response)
List<PersonInfo> ListA = new List<PersonInfo>() { new PersonInfo { Login = "1", FirstName = "James", LastName = "Watson", Active = true, Age = 21 }, new PersonInfo { Login = "2", FirstName = "Jane", LastName = "Morrison", Active = true, Age = 25 }, new PersonInfo { Login = "3", FirstName = "Kim", LastName = "John", Active = false, Age = 33 } };
List<PersonInfo> ListB = new List<PersonInfo>() { new PersonInfo { Login = "1", FirstName = "James2222", LastName = "Watson", Active = true, Age = 21 }, new PersonInfo { Login = "3", FirstName = "Kim", LastName = "John", Active = false, Age = 33 } };
//Get Items in ListA that are not in ListB
List<PersonInfo> FilteredListA = ListA.Except(ListB).ToList();
//To get the difference between ListA and FilteredListA (items from FilteredListA will be removed from ListA)
ListA.RemoveAll(a => FilteredListA.Contains(a));
EDIT
Try this soltuion for detail difference :
Compare two objects and find the differences
How to: Find the Set Difference Between Two Lists (LINQ)
Enumerable.Except Method (IEnumerable, IEnumerable) -Produces the set difference of two sequences by using the default equality comparer to compare values.
double[] numbers1 = { 2.0, 2.1, 2.2, 2.3, 2.4, 2.5 };
double[] numbers2 = { 2.2 };
IEnumerable<double> onlyInFirstSet = numbers1.Except(numbers2);
or
//newList will include all the common data between the 2 lists
List<T> newList = list1.Intersect(list2).ToList<T>();
//differences will be the data not found
List<T> differences = list1.RemoveAll(a => newList.Contains(a));
or
outer join to get difference
var compare1to2 = from a in
from b in driveList2.Where(b => b.property == a.property).DefaultIfEmpty()
select a;
var list3 = list1.Except(list2).ToList(); //List3 contains what in list1 but not _list2.
Try this for objects comparison and loop around it for List<T>
public static void GetPropertyChanges<T>(this T oldObj, T newObj)
{
Type type = typeof(T);
foreach (System.Reflection.PropertyInfo pi in type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
{
object selfValue = type.GetProperty(pi.Name).GetValue(oldObj, null);
object toValue = type.GetProperty(pi.Name).GetValue(newObj, null);
if (selfValue != null && toValue != null)
{
if (selfValue.ToString() != toValue.ToString())
{
//do your code
}
}
}
}
you can use Zip() and Intersect() from LINQ

Categories