Replace Duplicates with unique string in Dictionary<string,list<object>> - c#

I have List<ParametersDetails>. Parameters and ParametersDetails class is like -
public class ParameterDetails
{
public string Name { get; set; }
public List<Parameter> Parameters{get;set;}
}
public class Parameter
{
public string Name { get; set; }
public string Type { get; set; }
public string Value { get; set; }
public string AccountName { get; set; }
}
I want ParameterDetails list should not contain any parameter with duplicate name. If any duplicate parameter name found I want to replace the name with Parametername+ parameterDetails name from the dictionary.
I can do it by traversing items and then modify items but I want to do it with lesser code.
The problem is how to traverse and find duplicates from list ..?
Is it possible in Linq?
What I am doing right now - I have taken all the parameters in 1 list and find out duplicates
var hasDupes = dflist.GroupBy(x => new { x.Name })
.Where(x => x.Skip(1).Any()).ToArray();
Next, I am selecting the item from List
parameterdetails.Select(x => x.Parameters.Where(p => p.Name == dupeList.Key.ToString())).ToList();
Now I don't want to loop through ParameterDetials List to modify the items.
Is any easier way?
Ex-
I am having 2 items in ParameterDetails like -
ParameterDetails:
[
{
name: "test1",
Parameters:[
{
"Name":"param1",
"Type":"paramtype1",
"Value":"value1",
"AccountName":"accname1"
},
{
"Name":"param2",
"Type":"paramtype2",
"Value":"value2",
"AccountName":"accname2"
}]
},
{
name: "test2",
Parameters:[
{
"Name":"param1",
"Type":"paramtype11",
"Value":"value11",
"AccountName":"accname11"
},
{
"Name":"param2",
"Type":"paramtype22",
"Value":"value22",
"AccountName":"accname22"
}]
}]
If I am having param1 as a duplicate name so in that I want to replace it as "param1+test2" so that it will be unique.

Yo find they duplicated items in a list you can use LINQ, this is the code using it:
var duplicates = dflist.GroupBy(s => s) .SelectMany(grp => grp.Skip(1));
That code will return all duplicated items in dflist. If you want to select all object that have an unique attribute value, you can use this code line:
var withoutNameDuplicates = dflist.GroupBy(s => s.Name).Select(grp => group.First());
Then you can change the objects Name attribute to the duplicated objects by, using this code:
var nameChangedDuplicates = duplicates.Select( s => {s.Name = s.Name + "something"; }).ToList();
If you only want to get all the items from the list without duplicates, you can apply Dinstinct() method to dflist, that method will return an IEnumerable<T> result:
var withoutDuplicates = dflist.Distinct();
And, if you want to return a List<T> instead IEnumerable<T>, you must use ToList() method like this:
var withoutDuplicates = dflist.Distinct().ToList();
You can get more information about it in these pages:
c# - How to get duplicate items from a list using LINQ?
Enumerable.DistinctMethod (IEnumerable)
C# - Update all objects in a collection using LINQ

Related

Mongodb C# driver return only matching sub documents in array

I have a document in this format:
{
_id: ...,
myArray: [{other: stuff}, {other: stuff}, ...],
...
}
I want to find elements that match certain things, like the _id or fields value from the sub-documents in myArray.
I want to return the documents, but with a filtered MyArray where only the matching sub-documents are present.
I tried to do a projection and include the matched elements like this:
_mongoContext.myDocument
.Find(x => x.id == id & x.myArray.Any(y => myList.Contains(t.other)))
.Project<myModel>(Builders<myModel>.Projection.Include("myArray.$"))
This, I think, should only return the first element that matched in myArray instead of all documents, which is not what I want (I want all sub-documents that match the query to be present in the returned document).
And anyway it did not even work, I'm getting a positional projection does not match the query document error. Maybe it's because I'm not using FindOne?
In any case, how can I achieve what I'm looking for? (See question in bold)
Typically you need to use $filter in Aggregation Framework to filter nested array. However there's an easier way to achieve that using MongoDB .NET Driver and IQueryable interface.
Considering simplest model:
public class MyModel
{
public string _id { get; set; }
public IEnumerable<MyNestedModel> myArray { get; set; }
}
public class MyNestedModel
{
public string other { get; set; }
}
and following data:
var m = new MyModel()
{
_id = "1",
myArray = new List<MyNestedModel>() {
new MyNestedModel() { other = "stuff" },
new MyNestedModel() { other = "stuff" },
new MyNestedModel() { other = "stuff2" } }
};
Col.InsertOne(m);
you can simply call .AsQueryable() on your collection and then you can write LINQ query which will be translated by MongoDB driver to $filter, try:
var query = from doc in Col.AsQueryable()
where doc._id == "1"
select new MyModel()
{
_id = doc._id,
myArray = doc.myArray.Where(x => x.other == "stuff")
};
var result = query.ToList();
EDIT:
Alternatively you can write $filter part as a raw string and then use .Aggregate() method. Using this approach you don't have to "map" all properties however the drawback is that you're losing type safety since this is just a string, try:
var addFields = BsonDocument.Parse("{ \"$addFields\": { myArray: { $filter: { input: \"$myArray\", as: \"m\", cond: { $eq: [ \"$$m.other\", \"stuff\" ] } } } } }");
var query = Col.Aggregate()
.Match(x => x._id == "1")
.AppendStage<MyModel>(addFields);
$addFields is used here to overwrite existing field.

dynamically creating linq with mongodb

I've just started using mongodb in c# and it's great however I'm struggling to understand how i could dynamically create a linq query to pass to mongodb.
Situation: I have a file that has some general properties filename, filesize ect, one of these properties is metadata, which is a list of fields with values. the user will be able to specify the search criteria dynamically and so i cant hard code this query.
My Object for completeness:
public class asset
{
public ObjectId Id { get; set; }
public string filename { get; set; }
public int filesize { get; set; }
public List<shortmetadata> metadata { get; set; }
}
public class shortmetadata
{
public string id { get; set; }
public string value { get; set; }
}
My current code which is manually setting the search criteria and returns any asset that has "hello" or "world" in the metadata value field:
MongoClient client = new MongoClient();
var db = client.GetDatabase("Test");
var collection = db.GetCollection<asset>("assets");
var assets = collection.AsQueryable().Where(i =>
i.metadata.Any(m => m.value.Contains("hello")) ||
i.metadata.Any(m => m.value.Contains("world"))
);
What i would like to be able to do is dynamically create the query based on the users selection (don't have this yet as want to get it working in code first!)
Any help would be great.
If, for example, you had a Dictionary<string, string> containing the name value to search for keyed by the name of the meta item you could build your IQueryable<Asset> up in a loop like this
var query = collection.AsQueryable();
//Non-meta properties
query = query.Where(a => a.SomeNonMetaProperty == "Something");
//And now meta properties
foreach(var keyAndValue in someDictionary)
{
query = query.Where(m =>
m.Name == keyAndValue.Key
&& m.Value == keyAndValue.Value;
}
Slazure lets you create dynamic Linq queries at runtime since its predicates are string literals.
PM> Install-Package Slazure.MongoDB
// C# example: Build a document query that return employees that has a salary greater than $40k/year using a dynamic LINQ query filter.
dynamic storage = new QueryableStorage<DynDocument>("mongodb://user:pass#example.org/MongoDBExample");
QueryableCollection<DynDocument> employeesCollection = storage.Employees;
var employeeQuery = employeesCollection
// Query for salary greater than $40k and born later than early '95.
.Where("Salary > 40000 and Birthdate >= DateTime(1995,15,3)")
// Projection and aliasing.
.Select("new(_id as Email, Birthdate, Name, Timestamp as RegisteredDate)")
// Order result set by birthdate descending.
.OrderBy("Birthdate desc")
// Paging: Skip the first 5 and retrieve only 5.
.Skip(5).Take(5)
// Group result set on Birthdate and then on Name.
.GroupBy("Birthdate", "Name");
// Use a dynamic type so that we can get access to the document's dynamic properties
foreach (dynamic employee in employeeQuery)
{
// Show some information about the employee
Console.WriteLine("The employee '{0}' was employed {1} and was born in {2}.",
employee.Email, employee.RegisteredDate, employee.Birthdate.Year);
}
It also supports substitution values which makes your predicate code look cleaner.
// C# example: Query the storage for employee that earn less than $60k/yr and that are born before the millennium.
var amount = 60000;
var employeeQuery = employeesTable.Where("Salary > #0 and Timestamp <= #1", amount, new DateTime(2000, 1, 1));

Change the type of one of the objects property and sort it using LINQ

I want to sort the List where the objects properties are of string type.
One of the property is a time of string type, and when i try to sort it sorts like below.
1:12, 13:24, 19:56, 2:15, 26:34, 8:42.
Here the sorting is happening on string basis.
Now i want to convert that sting to double (1.12, 13.24, 19.56, 2.15, 26.34, 8.42) and sort it. Then populate the data by replacing the '.' with ':'.
I tried some thing like below, but still the sorting is happening on string basis.
public class Model
{
public string Duration { get; set; }
public string Dose { get; set; }
}
List<Model> lsModelData = new List<Model>();
//Added some model objects here
// query for sorting the lsModelData by time.
var sortedList = lsModelData.OrderBy(a => Convert.ToDouble(a.Duration.Replace(":", ".")));
I am trying to replace the time ":" with "." and then convert that to double to perform the sort operation.
Can any one please correct this statement to work this sorting properly.
If you want to sort data according to duration try this. its tested surely works for you.
public class Models
{
public string Duration { get; set; }
public string Dose { get; set; }
}
List<Models> lstModels = new List<Models>();
lstModels.Add(new Models { Duration = "101:12" });
lstModels.Add(new Models { Duration = "13:24" });
lstModels.Add(new Models { Duration = "19:56" });
List<Models> sortedList = (from models in lstModels
select new Models
{
Dose = models.Dose,
Duration = models.Duration.Replace(':','.')})
.ToList()
.OrderBy(x=>Convert.ToDouble(x.Duration))
.ToList();
I'm not sure what you really want, but if you want to return only the duration, then select it after sort
var sortedList = lsModelData.OrderBy(a => Convert.ToDouble(a.Duration.Replace(":", "."))).Select(a=> a.Duration).ToList();
or
var sortedList = lsModelData..Select(a=> a.Duration).OrderBy(a => Convert.ToDouble(a.Replace(":", "."))).ToList();
In cases like this it works best to order by length and then by content:
var sortedList = lsModelData.OrderBy(a => a.Duration.Length)
.ThenBy(a => a.Duration)
Converting database data before sorting (or filtering) always makes queries inefficient because indexes can't be used anymore.

Linq lambda using where

I am still learning lambda and Linq from Microsoft site and trying to write some simple example myself to get a deeper understanding of the cool stuff. The more I learn the more I find that stuff interesting but the learning curve is steep. Once again, I will need some more assistance.
Basically, I have class called item where it has properties NodeID, Weight and Category.
I have also a class called Recipient which represent recipient receiving items.
I have also a 2 dimensional boolean table that shows the interaction of one item against the other. If an item1 with ID NodeID1 is not supposed to have with item2 with ID Node2 then the table[Node1][Node2] should have a value true.
What I am trying to find out is the list of recipients that receive stuff that should not be receiving together, in other word stuff that has value true in the table.
public class Recipient
{
private Dictionary<int,item> _itemsReceivedList=new Dictionary<int,item>(); //itemID
private int _recipientID;
public int RecipientID{ get; set; }
public List<int> getListItemInCategory(int category)
{
return _itemsReceivedList.Where(x => x.Value.Category == category).Select(x => x.Value.NodeID).ToList();
}
}
public class item
{
public int NodeID { get; set; }
public int Weight { get; set; }
public int Category { get; set; }
}
In my main program:
private bool[][] prohibitedMatrix; //prohibitedMatrix[NodeID1][NodeID2]=true means it is prohibited to have Item NodeID1 and NodeID2 together
private Dictionary<int,Recipient> recipients = new Dictionary<int,Recipient>();
private Dictionary<int, item> items = new Dictionary<int,item>();
given an item with NodeID1, find recipients that has x in _itemReceivedList so that prohibitedMatrix[x.NodeID][NodeID1]= true
recipients.Where(x=>x.Value.getListItemInCategory(items[NodeID].Category)
&& "The NodeID in listItemInCategory and NodeID1 is not
true)
.Select(x=>x.Value.RecipientID)
Thank you for your help!
To have a one-liner this should work:
var result = recipients
.Values
.Select(r => new
{
RecipientID = r.RecipientID,
Items = r.getListItemInCategory(items[NodeID].Category)
})
.Where(ri => ri.Items.Any(i => prohibitedMatrix[i.NodeID][NodeID]))
.Select(ri => ri.RecipientID);
or this:
var result = recipients
.Values
.Where(r => r
.getListItemInCategory(items[NodeID].Category)
.Any(i => prohibitedMatrix[i.NodeID][NodeID]))
.Select(r => r.RecipientID);
However better to introduce some utility functions here and partition this. Or use plain-old foreach.
I think I found answer to my own question. I may or may not be correct. Please let me know if I am wrong.
This is what I think:
recipients.Where((x,y)=>x.Value.getListItemInCategory(items[NodeID1].Category).Contains(y) && prohibitedMatrix[y][NodeID1]).Select(x=>x.Value.RecipientID).ToList()

How can I turn an anonymous type into a key value pair object

I'm trying to figure out a clean way of getting data back from my web service that is key/value pair.
Is there a way to take a query like this:
var q = db.Ratings.Select(x => new
{
x.pro,
x.con,
x.otherThoughts,
x.Item.title,
x.score,
x.subject
});
And just return a key value pair where the key would be the columns listed above, with their corresponding value?
Rather than creating a separate class?
myDict.Add("pro", q.pro);
myDict.Add("con", q.con);
This is not efficient and creating a class like this isn't either, especially if I have dozens of methods:
public class Rating
{
public string pro { get; set; }
public string con { get; set; }
}
All of my searches turn up examples that contain the previous two code samples.
I don't recommend to use the 'untyped' approach that you are looking at.
I would use typed objects instead, the ones that you don't want to create.
However, here is the answer to your question. You can use DataTable object for what you need. Like this:
var items = new[]
{
new Item { Id = 1, Name = "test1" },
new Item { Id = 2, Name = "test2" }
};
var dataTable = new DataTable();
var propeties = typeof(Item).GetProperties();
Array.ForEach(propeties, arg => dataTable.Columns.Add(arg.Name, arg.PropertyType));
Array.ForEach(items, item => dataTable.Rows.Add(propeties.Select(arg => arg.GetValue(item, null)).ToArray()));
return dataTable;

Categories