Clone new object of old object without old reference in C# - c#

I have a huge object with a lot of attributes and child objects. Because of a poorly designed database which I can't control, I need to find matching objects in allCourses with the attribute CourseType = "SVYE". For those who matches the condition, I want to change all values from "SVYE" to "SVYR" instead and add them to the original object allCourses.
I realized that when you declare svCourse you still have the old references in courses which will cause all objects with the value on CourseType = "SVYR". Instead of every match should be one with CourseType = "SVYE" and one with CourseType = "SVYR".
How could I create a copy of the matching values without having the reference to allCourses in the new var svCourses without declaring every attribute again?
new Course(){
name = a.name
// etc..
}
My code:
var svCourses = allCourses.Where(x => x.Occasions
.Any(y => y.CourseType.Equals("SVYE")))
.ToList();
foreach(var svCourse in svCourses)
{
foreach(var o in svCourse.Occasions)
{
o.CourseType = "SVYR";
}
allCourses.Add(svCourse);
}
return allCourses;

If I understand you correctly, you want to keep Courses that have Occasions with CourseType.Equals("SVYE") unchanged in your collection and add a copy of that courses with modified only CourseType (CourseType = "SVYR"). Right?
If so, you must somehow deep-clone the svCourses collection with 3-rd party library:
Json.NET NuGet:
var svCourses = allCourses.Select(x => new
{
Course = x,
MatchedOccasions = x.Occasions
.Where(y => string.Equals(y.CourseType, "SVYE"))
.ToArray()
})
.Where(x => x.MatchedOccasions.Length > 0)
.ToList();
var clonedSvCourses = JArray.FromObject(svCourses.Select(x => x.Course).ToList()).ToObject<List<Course>>();
allCourses.AddRange(clonedSvCourses);
foreach(var occasion in svCourses.SelectMany(x => x.MatchedOccasions))
{
occasion.CourseType = "SVYR";
}
return allCourses;
FastDeepCloner NuGet I think, can be used too.

I found a solution on my own!
I used a third-party library called CloneExtensions. This library could be used to create a deep copy of your object without declaring every attribute. This new variable newCourses which is a deep copy of svCourses doesn't have the old reference to allCourses, which solves the problem.
This solution will replace o.CourseType = "SVYR"; for all ocassions in the variable newCourses.
var svCourses = allCourses.Where(x => x.Occasions
.Any(y => y.CourseType.Equals("SVYE")))
.ToList();
var newCourses = svCourses.Select(x =>
CloneExtensions.CloneFactory.GetClone(x));
foreach(var svCourse in newCourses)
{
foreach(var o in svCourse.Occasions)
{
o.CourseType = "SVYR";
}
allCourses.Add(svCourse);
}
return allCourses;

Any means Determines whether any element of a sequence satisfies a condition.
What you need is
List all occasions of all courses.
Choose occasions that CourseType = SVYE
var svCourses = allCourses
.SelectMany(c => c.Occasions)
.Where(o => o.CourseType.Equals("SVYE"))
.ToList();

Related

How do I copy projected results into another variable in c#?

The code below wont run, as it complains that I am trying to add a type Anon into a type of Clients. How can I store certain results in another variable after projecting them originally and having lost the original Type.
(PS. I have made my example simple but am actually dealing with a more complex case. Not projecting is not an option in my actual case. Edited to provide clarification.)
var clients = Clients.Where(c => c.FirstName.StartsWith("Mark"))
.Select(c => new {
LastName = c.LastName.ToUpper(),
c.DateAdded,
c.FirstName,
})
.ToList();
var certainClients = new List<Clients> { };
foreach (var client in clients)
{
if(client.DateAdded.Date < DateTime.Today) {
certainClients.Add(client);
}
}
certainClients.Dump();
There are two options.
First. Instead of using an anon data type, use Clients datatype. As in effect you are creating Clients object -
var clients = Clients.Where(c => c.FirstName.StartsWith("Mark"))
.Select(c => new Clients{
LastName = c.LastName.ToUpper(),
c.DateAdded,
c.FirstName,
})
Second. Create a list of object and assign whatever custom/anon data type to it -
var certainClients = new List<object> { };
The best way is to project to a custom business entity class.
This means that we actually define the class ourselves. For example.
public class ClientEntity
{
public string LastName;
public DateTime DateAdded;
// etc for custom fields or properties you want
}
Then we can simply project to our custom made class
var clients = Clients.Where(c => c.FirstName.StartsWith("Mark"))
.Select(c => new ClientEntity{
LastName = c.LastName.ToUpper(),
DateAdded = c.DateAdded,
etc
})
This way it avoids List <object> which is not type safe and doesn't need to be similar to the original client class for example if we want the length of the original name.

LInq Contains Operator Shows Design Time Error

I'm trying to filter a collection based on the contents of another collection using linq and EF 6.2.0. However, I am getting a design time error "Cannot resolve method Contains(string)". I could have sworn I used this in the past but it's not working now. What did I miss?
private void UpdateCheckListItems(LMUpdateModel model, LoanResponse response)
{
var checkListItems = this.appData.LMLoanChecklist.Find(x => x.LMAutoID == model.LMAutoID);
var codesToUpdate = checkListItems.Where(x => model.CheckListItems.Contains(x.LMCLCode));
... other code
}
So I looked back over some other projects and found a sample of when I implemented it before. I solved it by loading the values I was searching into a list of List<T> and then adding the Contains on the new list. I thought you could use the value from a complex list but I guess not.
List<string> lmlcCodes = new List<string>();
foreach (var item in model.CheckListItems)
{
var code = item.LMCLCode;
lmlcCodes.Add(code);
}
var checkListItems = this.appData.LMLoanChecklist.Find(x => x.LMAutoID == model.LMAutoID).Where(x => lmlcCodes.Contains(x.LMCLCode));
I did find though that If I dropped the .Contains() and add the .Any() then this statement does work with the complex object.
var checkListItems = this.appData.LMLoanChecklist.Find(x => x.LMAutoID == model.LMAutoID).Where(cl => model.CheckListItems.Any(cl1 => cl1.LMCLCode == cl.LMCLCode)).ToList();

C# using aggregate method on nopcommerce shipment items list not working

Ok so I'm trying to get all items which have been shipped and add them to a list if the shipment item is not already in that list. But if the shipment item is found in the list then I want to combine those two items in the list.
Here is the code I'm working with:
var shippedItems = _orderService.GetOrderById(shipment.OrderId).Shipments.Where(x => x.ShippedDateUtc != null && x.OrderId == shipment.OrderId && x.Id != shipment.Id).ToList();
List<ShipmentItem> shipmentItemsList = new List<ShipmentItem>();
for (int i = 0; i <= shippedItems.Count - 1; i++)
{
var si = shippedItems[i];
var sii = si.ShipmentItems.ToList();
foreach (var item in sii)
{
if (!shipmentItemsList.Contains(item))
{
shipmentItemsList.Add(item);
}
else
{
var foundId = shipmentItemsList.Select(x => x.Id == item.Id);
shipmentItemsList.Aggregate((foundId, item) => foundId + item);
}
}
}
For these two variables (foundId, item) i get errors:
A local variable named the variable name cannot be declared in this
scope because that name is used in an enclosing local scope to define
a local or parameter
UPDATE
I also thought I could try the following, but it's not joining the results.
if (i == 0)
{
shipmentItemsList = si.ShipmentItems.ToList();
}
else
{
shipmentItemsList.Concat(si.ShipmentItems.ToList());
}
Anyone able to point me on the right track.
Cheers
Thanks for the clarification. Essentially, the way that I understand your problem is that you need to take an object map that is grouped by Shipment and look at it from the point of Item instead. Linq can deal with this for you by using SelectMany to flatten the list and the GroupBy to shape the flattened list into your new groupings. I've made some assumptions about property names for the nopCommerce objects, but the following code sample should get you close enough to tweak with the correct property names:
var shipmentItemsList = shippedItems // This is logically grouped by shipment id
.SelectMany(s => s.ShipmentItems) // First flatten the list
.GroupBy(i => i.ItemId) // Now group it by item id
.Select(g => new
{
ItemId = g.Key,
Quantity = g.Sum(item => item.Quantity)
}) // now get the quantity for each group
.ToList();

Splitting LINQ results into array based on column

I am trying to select a few columns from a single row using LINQ to Entities and then split each column into its own point of an array.
I have the LINQ query getting the results I want, but I can't figure out how to split it into the array. I thought using .ToArray() would work but it doesn't split them.
var LinkData = db.Sections
.Where(s => s.ID == SectionID)
.Select(s => new
{
s.Type,
s.Route
}).ToArray();
How can I split the results from the query so I have a single array of two elements: one for Type and one for Route?
Your Select-statement already creates a list of two-value-items which are stored in instances of anonymous type. So there is no need to create a new two-dimensional array for this. Your linkdata already contains the data you want, however if you´re after one specific combination of (Type, Route) simply call linkedData[myIndex].Route or linkedData[myIndex].Type respectively.
EDIT: If you really want arrays then the following should work:
var arr = linkedData.Select(x => new[] { x.Rote, x.Type }).ToArray();
Which will give you an array of arrays where every element itself contains an array of two elements.
var section = db.Sections
.Where(s => s.ID == SectionID)
.Select(s => new
{
s.Type,
s.Route
})
.SingleOrDefault();
var LinkData = new [] {section.Type, section.Route};
Use a List<> instead
Thats what i would do.
Create a public class of Link Data so:
public class LinkData
{
public string type {get; set;}
public string Route {get;set;}
}
Then in your code
create a List of the Link Data:
List<LinkData> LinkDataList = new List<LinkData>();
then create an object
LinkData obj = new LinkData
add stuff to the object
obj.type = db.Section.Where(s => s.ID==SectionID).Select s=> s.Type new s.Type).SingleOrDefault();
obj.Route = db.Section.Where(s => s.ID==SectionID).Select s=> s.Route new s.Type).SingleOrDefault();;
LinkDataList.Add(obj)
This should give you a clear indication of whats what :)

Merge two lists of objects which hold different data

I have an object Project which has a number of fields, one of which is Name. I have one spreadsheet of projects which contains some of these fields, and another which contains the rest. However, both spreadsheets have the Name field.
I've read them in and populated two List<Project>, only populating the available fields from that particular source. E.g. a Project from List 1 looks like:
{Name="MyProj", Type="Form", Priority=NULL}
Whereas a Project from List 2:
{Name="MyProj", Type=NULL, Priority="High"}
Now, I want to merge these two lists into one in which each Project object has all of its fields populated, with the Name field being used to match the elements.
How can I achieve this? Are there any nice ways of doing this concisely?
Thanks
I would probably use the ?? operator to find the values. A dictionary could be useful.
// put one into a dict for faster access
var dict2 = list2.ToDictionaty(x => x.Name, x);
// merge the lists using ??
var merged = list1.Select(x =>
{
var p2 = dict2[x.Name];
return new Project()
{
Name = x.Name,
Type = x.Type ?? p2.Type,
Priority = x.Priority ?? p2.Priority
}
});
Or
var merged = list1
// join the lists by the name
.Join(list2, x => x.Name, x => x.Name, (p1, p2) => new { P1 = p1, P2 = p2 } )
.Select(x =>
new Project()
{
Name = P1.Name,
Type = P1.Type ?? P2.Type,
Priority = P1.Priority ?? P2.Priority
});
There are hundred of variants of this. You could only join and and process the result in a foreach to merge the data of one list into the other. Etc.
It gets more complicated when the projects could be missing in one of the lists, and even more complicated if none of the lists is complete. Then it could be useful to create a complete list of project names at the beginning.

Categories

Resources