Depth of a relationship - c#

I'm trying to figure out how to get the depth of a relationship of an entity that can relate to itself. That is There is a base comment item, which can have one or more reply comments linked to it.
Now for each one of these replied comments I want to know how deep they are from the top node.
ie, I want to know the Depth in the comments in the image below:
This is the code as of now, and the variable ResponseTo is the relationship that this comment is a response to, if is is not a response the value is null
var comments = db.Comments
.Where(c => c.Post.ID == id && c.Status == Helpers.StatusCode.Visible)
.Select(x => new CommentTemp()
{
Id = x.ID,
Text = x.Text,
Avatar = "-1",
Username = (x.User != null ? x.User.UserName : "Anonymous"),
UserID = (x.User != null ? (int?) x.User.Id : null),
ResponseTo = (x.ResponseTo == null ? null : (int?) x.ResponseTo.ID),
CommentDepth = ??? // <--- how do i select the depth of the response to relations?
Created = x.Created
})
.ToList();
Only option I can think of right now is saving it as the comment is created, but if possible I would like to get it on the fly.

Well, it indeed sounds like you want to hold such information in the database, unless there's really good reason not to. Either way, Linq is not really recursive friendly.
You can get the depth by creating dictionary & tracking it back recursively.
int GetDepth(Comment comment,
IDictionary<int, Comment> comments, /*key=commentId, val=Comment*/
IDictionary<int, int> depthMemoization, /* key=commentId, val=depth*/
int currentDepth = 0)
{
if(depthMemoization.ContainsKey(comment.Id))
return depthMemoization[comment.Id];
if(comment.ParentId==null)
return currentDepth;
var parentComment = comments[comment.ParentId.Value];
int calculatedDepth = GetDepth(parentComment, comments,
depthMemoization,
++currentDepth);
depthMemoization[comment.Id] = calculatedDepth ;
return depth;
}

Related

best way to check internal objects of list are null

I am forming an object based on list coming from input along with the string like as below.
public static LibrarySourceTableInput CreateLibrarySourceTableInput<T>(List<T> libraries, string mechanicalLibraryName)
where T : ISourceOfData
{
return new LibrarySourceTableInput()
{
LibrarySourceRowInputs = libraries?.Select(l => new LibrarySourceRowInput()
{
LibrarySourceId = l.Id,
SourceOfDataId = l.SourceOfData.Id
}).ToList() ?? new(),
MappedLibrarySource = mechanicalLibraryName
};
}
I am facing different problem here, I have libraries count coming as 1 but internal object is null for example in the below image libraries count as 1 and it is null,
and in this case I am getting a null reference exception with the above code, could any one please help on this how to avoid that null exception. Many thanks in advance.
You could do a where query before the select.
LibrarySourceRowInputs = libraries?.Where(l => l != null)
.Select(l => l => new LibrarySourceRowInput()
{
LibrarySourceId = l.Id,
SourceOfDataId = l.SourceOfData.Id
}).ToList() ?? new();

Returning IEnumerable<> for a model in ASP.NET Web API

I am trying to get a list whose type is a model(called ItemDetail). However, I get this error: "Object reference not set to an instance of an object."
public class ItemDetail
{
public decimal sum { get; set; }
public string username { get; set; }
public string units { get; set; }
public string remarks { get; set; }
}
The API code is as follows:
[HttpGet]
[Route("api/items/details/{id}")]
public IEnumerable<ItemDetail> ItemDetails(int id)
{
using (ShopEntities entities = new ShopEntities())
{
var itemDetails = entities.vwItemDetails.ToList();
var userIds = from data in itemDetails
select data.Userid;
var item_Details = new List<ItemDetail> ();
foreach (int userId in userIds)
{
int totals = (int)entities.vwItemDetails
.Where(i => i.UserID == userId & i.item_id == id)
.Select(i => i.quantity)
.DefaultIfEmpty(0)
.Sum();
var itemRecord = entities.vwItemDetails.SingleOrDefault(i => i.item_id == id & i.Userid == userId);
item_Details.Append(new ItemDetail
{
username = itemRecord.username,
units = itemRecord.units,
remarks = itemRecord.Remarks,
sum = totals
});
}
return item_Details;
}
}
Thanks in advance for your help.
EDIT: The error occurs inside the foreach loop where I'm trying to append the new ItemDetail (item_Details.Append(new ItemDetail)
I think I see the problem...
var itemRecord = entities.vwItemDetails.SingleOrDefault(i => i.item_id == id & i.Userid == userId);
Your filtering for the SingleOrDefault() is using a bitwise AND operator instead of boolean one. The value of itemRecord as it's written right now is almost certainly always null. Try changing that line to this:
var itemRecord = entities.vwItemDetails.SingleOrDefault(i => i.item_id == id && i.Userid == userId);
EDIT:
I just realized you do the same thing in this LINQ area:
int totals = (int)entities.vwItemDetails
.Where(i => i.UserID == userId & i.item_id == id)
.Select(i => i.quantity)
.DefaultIfEmpty(0)
.Sum();
Again, totals is probably coming up as 0.
EDIT 2:
There is more wrong here than I original anticipated. I created a semi-complete working sample and you've got problems beyond the use of the bitwise operator.
As orhtej2 pointed out in the comments, you are setting yourself up to return null, but you don't check for it. So that is the immediate cause of your exception. You're probably iterating through a list of user IDs where some of the IDs aren't linked to the item ID you're working with. That will return a null because of SingleOrDefault.
The fix for that is to check if itemRecord is null, and if so, do continue; to move onto the next user ID. Or if you want to stop processing and return an error, do that. Either way the situation should be handled.
Related to that is another consequence of using SingleOrDefault. A friend of mine pointed out that if you end up with more than one result in your where clause there, you will get an exception as well. Unless you can guarantee that no single user ID will have more than one instance of a given item ID in that collection of item details, you need to use a different method. The most straightforward would be to use Where() instead, and handle the IEnumerable<> that it returns. You'll have another loop, but that's showbiz.

Return Object with different scope based on Conditional Logic (C#)

I've been a developer (also in a professional capacity) for a little while now and really never focused on clean / well structured code. As completely self taught I guess I'm missing some of the fundamentals. Reading books never fills the gaps. So I hope to get some great experience from this post.
So to the point, I have a method that returns an object (Campaign) based on conditional logic.
If I can get the object via the "CampaignViewMode" then it must have been "viewed" so therefore GET
ELSE Get last inserted
1, Get if it has been recently viewed (Cookie)
2, Else just get the last Inserted.
Pretty basic but the code has a serious "code smell" (repetition). In an ideal world I'd like to remove the conditional.
public Campaign GetDefaultCampaign()
{
Campaign campaign = null;
using (UserRepository userRepo = new UserRepository())
{
var user = userRepo.GetLoggedInUser();
if (user != null)
{
string campaignViewMode = "";
if (HttpContext.Current.Request.Cookies["CampaignViewMode"] != null)
{
campaignViewMode = HttpContext.Current.Request.Cookies["CampaignViewMode"].Value.ToString();
}
//Get Last worked on/viewed
campaign = _context.tbl_Campaign
.Where(x => x.Name == campaignViewMode & x.tbl_UserToCampaign
.Where(z => z.UserId == user.UserId & z.CampaignId == x.CampaignId)
.Select(u => u.UserId)
.FirstOrDefault() == user.UserId)
.Select(y => new Campaign()
{
CampaignId = y.CampaignId,
Name = y.Name,
WebName = y.WebName,
DateAdded = y.DateAdded
}).FirstOrDefault();
//Or get last inserted
if (campaign == null)
{
campaign = _context.tbl_Campaign
.Where(x => x.Name == campaignViewMode & x.tbl_UserToCampaign
.Where(z => z.UserId == user.UserId & z.CampaignId == x.CampaignId)
.Select(u => u.UserId)
.OrderByDescending(d => d.DateAdded).FirstOrDefault() == user.UserId)
.Select(y => new Campaign()
{
CampaignId = y.CampaignId,
Name = y.Name,
WebName = y.WebName,
DateAdded = y.DateAdded
}).FirstOrDefault();
}
}
}
return campaign;
}
Could you kindly point me in the right direction of removing the conditional or at the very last reduce the "smell" ?
Fully appreciate your time!
Regards,
There's alot going on here. Here's what I'd do.
Don't new up instances (as you do with Repository). Code against abstracts (IRepository) which is provided by a DI container which is injected into the class constructor.
Remove the duplication that maps your data model to your returned model (Select(x=> new Campaign()). Extract this out as a method or a separate responsibility entirely.
Remove the huge nested if(user!=null). Check for this right up front and return if it is null.
Refactor the two fetching operations behind an interface (IGetCampaigns) and create two classes; one that fetches the latest inserted, and one that fetches the last viewed/worked on. Inject one into the other to form a decorator chain, wired up by your DI container.
Probably a lot to take in if you're unfamiliar with these concepts; happy to go through this offline if you'd like.

null issue during usage of count and sum LINQ

I have three tables and they are linked like
grandparent -> parent -> child
categoryType - > Categories - > Menus
when I try to run following
return categoryTypes.Select(x =>
new CategoryTypeIndexModel
{
Id = x.Id,
Name = x.Name,
Categories = x.Categories.Count,
Items = x.Categories.Sum(m => m.Menus.Count())
});
I get
The cast to value type 'System.Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.
You are trying to count something that isn't there when Categories is null.
I believe what Habib recommend would technically work but you would still have to account for a null value after the fact.
I think the better solution would be to account for it in your linq directly by looking for null and providing a default
return categoryTypes.Select(x => new CategoryTypeIndexModel
{
Id = x.Id,
Name = x.Name,
Categories = (x.Categories == null) ? 0 : x.Categories.Count,
Items = (x.Categories == null) ? 0 : x.Categories.Sum(m => m.Menus.Count())
});
If Menus could ever be null you would also need to account for that in a similar fashion

linq statuses must return distinct

I have a DB table that looks similar to this.
ID | Status | Type
etc...etc
I am using linq to try and discern distinct Statuses from this collection like so
results = ctx.Status.Distinct(new StatusComparer()).ToList();
but this returns all statuses, I used the following Link to construct the Comparer below, I found that suggestion in another StackOverflow Post
public class StatusComparer : IEqualityComparer<Status>
{
public bool Equals(Status x, Status y)
{
// Check whether the compared objects reference the same data.
if (ReferenceEquals(x, y))
{
return true;
}
// Check whether any of the compared objects is null.
if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
{
return false;
}
// Check whether the status' properties are equal.
return x.StatusDescription == y.StatusDescription && x.Type == y.Type && x.StatusID == y.StatusID;
}
public int GetHashCode(Status status)
{
// Get hash code for the Name field if it is not null.
var hashStatusId = status.StatusID.GetHashCode();
// Get hash code for the Code field.
var hashStatusDescription = status.StatusDescription.GetHashCode();
var hashStatusType = status.Type.GetHashCode();
// Calculate the hash code for the product.
return hashStatusId ^ hashStatusDescription ^ hashStatusType;
}
}
}
My problem is as follows early on we had a system that worked fine, so well in fact they wanted another system using the same Database so we plumbed it in. The search has an advanced options with several filters one of them being Status but as you can see from the above (loose) DB structure statuses have different types but similar text. I need to be able to select via Linq the whole status by the distinct text. all help would be greatly appreciated.
have also tried
results = (from s in context.Status group s by s.StatusDescription into g select g.First()).ToList();
this also failed with a System.NotSupportedException
To select all distinct statuses:
ctx.Status.Select(s => new { s.StatusDescription, s.Type }).Distinct();

Categories

Resources