Nested Object from DataTable - c#

I feel like I am way off track here. I have been banging my head against my keyboard and I do not feel like I am any closer to a solution. Guidance would be much appreciated.
Models.Content contains a SubContent property that is of type List<Models.Content>. The nesting could be infinite and should be recursive. I feel like this should be able to be done with a few lines of LINQ and while or something to that affect. I have created a mess.
private static List<Models.Content> GetAllContentFromDataSet(DataSet ds)
{
var content = new List<Models.Content>();
var contentList = (from DataRow row in ds.Tables[0].Rows
select new Models.Content
{
Id = Convert.ToInt32(row["Id"]),
ParentContentId = Convert.ToInt32(row["ParentContentId"]),
c3 = Convert.ToString(row["c3"]),
c4 = Convert.ToString(row["c4"]),
c5 = Convert.ToString(row["c5"])
}).ToList();
content.AddRange(NestContent(contentList));
return content;
}
private static IEnumerable<Models.Content> NestContent(List<Models.Content> content)
{
var toBeRemoved = new List<Models.Content>();
foreach (var c in content)
{
var parent = content.FirstOrDefault(p => p.Id == c.ParentContentId);
if (parent == null) continue;
parent.SubContent.Add(c);
toBeRemoved.Add(c);
}
foreach (var c in toBeRemoved)
{
content.Remove(c);
}
return content;
}

Here's an example of what I would do:
// Create a list of all items without children.
var things = table.AsEnumerable()
.Select(row => new Thing
{
Id = row.Field<int>("Id"),
ParentId = row.Field<int>("ParentId")
})
.ToList();
// Add children to each item.
things.ForEach(t1 => t1.Children = things.Where(t2 => t2.ParentId == t1.Id).ToList());
// Create a list of items that don't have a parent..
things = things.Where(t => t.ParentId == 0).ToList();

I agree, I think you could build your model hierarchy with fewer lines of code, thus, making your code easier to maintain and read. I'd actually do this in two steps...
Parsing the root "content" first (those with ParentContentId == null...I guess)...this can be done in one line of code with LINQ
Then iterate through all child content rows (those with a ParentContentId specified) and "attach" them to the parent content (if exists)
Let me know if you got the idea or if you need an example

Related

How to find if item in one Observable Collection?

I would like to request you to help me with this problem. I have an Observable Collection that stores favourites and I load items to another Observable Collection, so, what I want is for the new items which have unique IDs to be checked against Favourites Observable Collection in an efficient way as I feel what I am doing is not good enough.
What I am currently doing is as follows:
foreach (var item in AllItems)
{
if (Watchlist.Fav.Count != 0)
{
if (Watchlist.Fav.Any(s => s.Id == item.Id)))
{
Watchlist.Fav.Remove(Watchlist.Fav.SingleOrDefault(i => i.Id == item.Id));
Watchlist.Fav.Add(item);
item.IsFavorite = true;
ItmesCollection.Add(item);
}
}
}
Could someone please help me make this better?
Cheers guys!
Since OP is asking a better syntax of current working code. Here is a try:
foreach (var item in AllItems)
{
// no need check Watchlist.Fav.Count, because .Any duplicates
// store matchingFav in variable so no need query twice
var matchingFav = Watchlist.Fav.SingleOrDefault(s => s.Id == item.Id)));
if (matchingFav != null)
{
matchingFav.IsFavorite = true;
// where is ItemsCollection from? background not clear, so no change
ItemsCollection.Add(item);
}
}
If the last statement is not relavant, syntax can be even shorter:
var ids = new HashSet<int>(AllItems.Select(x => x.Id));
foreach (var matchingFav in Watchlist.Fav
.Where(x => ids.Contains(x.Id)))
{
matchingFav.IsFavorite = true;
}

Finding objects in a collection that have any string from a list in a subcollection

Ok, this one has me stumped.
I have a collection of objects called Interviews. An Interview has a collection of Notes in it. A Note has string (nvarchar(max) on the database) property called NoteText.
I have a List called keywords.
What I need to do is find all interviews that have a Note that has any of the keywords within its NoteText property.
I have this so far:
var interviewQuery =
from i in dbContext.Interviews //dbContext was created with Telerik OpenAccess
.Include(n => n.Notes)
.Where(i => i.Notes.Any(n => keywords.Contains(n.NoteText) ))
orderby i.WhenCreated descending
select i;
I don't get an error, I just don't get any results either.
I'm pretty poor at linq, but this can be easily done with a loop instead.
var matchinginterviews = new List<Interview>();
foreach (var inter in MyInterviewEnumerable)
{
foreach (var note in inter.NoteCollection)
{
foreach (string keyword in keywordList)
{
if (note.NoteText.IndexOf(keyword) != -1)
{
matchinginterviews.Add(inter);
}
}
}
}
What's causing the empty results is that you're looking for any keyword values that contain the entire content of any of the notes.
We made an extension method ContainsAny:
public static bool ContainsAny(this string s, IEnumerable<string> possibleContained)
{
foreach (string p in possibleContained)
{
if (s == p) return true;
if (s == null) continue;
if (s.Contains(p)) return true;
}
return false;
}
Then you could do something similar to where you started:
var results = dbContext.Interviews.Where(i => i.Notes.Any(n => n.NoteText.ContainsAny(keywords)));

Condense similiar methods into one mega cool method

I'm trying to remove duplicated code and run into an issue here:
I've got five very similar entities (different asset types, e.g. Bonds, Stocks). The methods I'm trying to condense return some statistics about these assets. The statistics are obtained with the help of Linq, the queries are almost identical.
Before, I had five separate methods in my controller (e.g. BondStatistics, StockStatistics). One of these would look like this (db is my database context which has each asset type defined):
public JsonResult BondStatistics()
{
var items = db.Bonds.ToList();
var result = new[]
{
new
{
key = "Bonds",
values = items.Select(i =>
new {
x = i.priceChangeOneDayInEuro,
y = i.priceChangeTotalInEuro,
size = i.TotalValueInEuro,
toolTip = i.Description
}
)
},
};
return Json(result, JsonRequestBehavior.AllowGet);
}
I googled that one way to rewrite these into just one method could be using reflection. However, I thought I could use a dirty shortcut, something like this:
public JsonResult Scatter(string asset)
{
if (asset == "Stocks") { var items = db.Stocks.ToList(); };
if (asset == "Bonds") { var items = db.Bonds.ToList(); };
if (asset == "Futures") { var items = db.Futures.ToList(); };
if (asset == "Options") { var items = db.Options.ToList(); };
if (asset == "Funds") { var items = db.Funds.ToList(); }
var result = new[]
{
new
{
key = asset,
values = items.Select(i =>
new {
x = i.priceChangeOneDayInEuro,
y = i.priceChangeTotalInEuro,
size = i.TotalValueInEuro,
toolTip = i.Description
}
)
},
};
return Json(result, JsonRequestBehavior.AllowGet);
}
This leads to the problem that the type of "items" is not known in the Linq query at design time.
What would be a good way to overcome this problem? Use some totally other pattern, do use reflection or is there an easy fix?
EDIT
As suggested, I created an Interface and let the BaseAsset-class implement it. Then, changing the condensed method to
List<IScatter> items = new List<IScatter>();
if (asset == "Stocks") { items = db.Stocks.ToList<IScatter>(); };
if (asset == "Bonds") { items = db.Bonds.ToList<IScatter>(); };
if (asset == "Futures") { items = db.Futures.ToList<IScatter>(); };
if (asset == "Options") { items = db.Options.ToList<IScatter>(); };
if (asset == "Funds") { items = db.Funds.ToList<IScatter>(); }
works, at design time at last. Thank you very much!
You are putting everything into var, but what exactly is the type of the items you are processing?
If it would be List<Stock> for db.Stocks.ToList(), List<Bond> for db.Bonds.ToList() you can simply define an interface (e.g. IHasPriceInformation) which has the fields you are using in the LINQ query. Then, Let Stock, Bond and others implement this interface (or provide an abstract base implementation of them) and simply run your LINQ Query on a List<IHasPriceInformation>.

Find a dupe in a List with Linq

I am building a list of Users. each user has a FullName.
I'm comparing users on FullName.
i'm taking a DataTable with the users from the old DB and parsing them to a 'User' Object. and adding them in a List<Users>. which in the code is a List<Deelnemer>
It goes like this:
List<Deelnemer> tempDeeln = new List<Deelnemer>();
bool dupes = false;
foreach (DataRow rij in deeln.Rows) {
Deelnemer dln = new Deelnemer();
dln.Dln_Creatiedatum = DateTime.Now;
dln.Dln_Email = rij["Ler_Email"].ToString();
dln.Dln_Inst_ID = inst.Inst_ID;
dln.Dln_Naam = rij["Ler_Naam"].ToString();
dln.Dln_Username = rij["LerLog_Username"].ToString();
dln.Dln_Voornaam = rij["Ler_Voornaam"].ToString();
dln.Dln_Update = (DateTime)rij["Ler_Update"];
if (!dupes && tempDeeln.Count(q => q.FullName.ToLower() == dln.FullName.ToLower()) > 0)
dupes = true;
tempDeeln.Add(dln);
}
then when the foreach is done, i look if the bool is true, check which ones are the doubles, and remove the oldest ones.
now, i think this part of the code is very bad:
if (!dupes && tempDeeln.Count(q => q.FullName.ToLower() == dln.FullName.ToLower()) > 0)
it runs for every user added, and runs over all the already created users.
my question: how would I optimize this.
You can use a set such as a HashSet<T> to track unique names observed so far. A hash-set supports constant-time insertion and lookup, so a full linear-search will not be required for every new item unlike you exising solution.
var uniqueNames = new HashSet<string>(StringComparer.CurrentCultureIgnoreCase);
...
foreach(...)
{
...
if(!dupes)
{
// Expression is true only if the set already contained the string.
dupes = !uniqueNames.Add(dln.FullName);
}
}
If you want to "remove" dupes (i.e. produce one representative element for each name) after you have assembled the list (without using a hash-set), you can do:
var distinctItems = tempDeeln.GroupBy(dln => dln.FullName,
StringComparer.CurrentCultureIgnoreCase)
.Select(g => g.First());
Try this out--
http://blogs.msdn.com/b/ericwhite/archive/2008/08/19/find-duplicates-using-linq.aspx
Count will go through whole set of items. Try to use Any, this way it will only check for first occurrence of the item.
if (!dupes && tempDeeln.Any(q => q.FullName.ToLower() == dln.FullName.ToLower()))
dupes = true;

A LINQ query with grouping and filtering of special groups

I have a class...
class Document
{
public int GroupID { get; set; }
public bool Valid { get; set; }
// more
}
... and a list of instances: IEnumerable<Document> documents. In a first step which runs object by object through this list those documents have been validated, which means: the property Valid will be true for some objects and false for other objects in the list.
Now in a second step I have to do the following:
If for at least one document per document group (defined by all documents with the same GroupID) the flag Valid is false then set Valid to false for all documents of the group.
To do this I have created so far the following code fragment:
var q = from d in documents
group d by d.GroupID;
// q is now of type IEnumerable<IGrouping<int, Document>>
foreach (var dg in q) // dg = "document group", of type IGrouping<int, Document>
{
if (dg.Any(d => !d.Valid))
{
foreach (var d in dg)
d.Valid = false;
}
}
I believe, this does what I want (I didn't test it until now, though) but not very efficiently.
Question: Is there a way to improve this code, especially to move the semantics of the Any method in the outer foreach loop "somehow" into the initial LINQ query, so that q only represents the groups which have at least one invalid document? (Also I am obviously not interested in groups which have only one element, so those groups could be filtered out as well.)
Thank you for suggestions in advance!
I think this does what you want:
var q = from d in documents
group d by d.GroupID into g
where g.Count() > 1 && g.Any(d => !d.Valid)
select g;
foreach (var dg in q)
{
foreach (var d in dg)
{
d.Valid = false;
}
}
The top part in fluent syntax would look like:
var q = documents.GroupBy(d => d.GroupID)
.Where(g => g.Count() > 1 && g.Any(d => !d.Valid));
If you're just trying to set the Valid flag to false if one of the documents is not Valid, you could try grabbing the list of the GroupId's who are not valid and then set all of the documents whose share a group with them to be invalid. Sample code as follows:
//Find the invalid GroupIds.
var invalidIds = documents.Where(d => !d.IsValid).Select(p => p.GroupId).Distinct();
//invalidIds now holds the bad groupIds.
//So we can find out if each document's GroupId is an invalid one, and if it is, mark it as invalid.
documents.Where(d => invalidIds.Contains(d.GroupId)).ToList().ForEach(p => p.IsValid = false);
Hope this helps.

Categories