Consecutive IDs in a navigation property List - c#

I'm trying to make a discussion forum in ASP.NET MVC 5 (mostly as a test as I'm pretty new to C#/MVC/any coding).
I have two classes, Discussion and Message.
public class Discussion
{
public int DiscussionID { get; set; }
[Required]
[Display(Name="Discussion Title")]
public string DiscussionTitle { get; set; }
//[Required]
//ForumUser UserCreatedThread { get; set; }
[Required]
DateTime CreatedTime { get; set; }
public ICollection<Message> Messages { get; set; }
}
and
public class Message
{
public int MessageID { get; set; }
public int MessageNumber { get; set; }
[Required]
[Display(Name="Message Content")]
[DataType(DataType.MultilineText), AllowHtml]
public string Content { get; set; }
[Required]
public DateTime MessageTime { get; set; }
public virtual int DiscussionID { get; set; }
}
Discussion has a list of Messages which I would like the MessageID, or another property to be ordered 1,2,3,etc for each list. Currently if more than one discussion is created the MessageID can be 1,4,5,8,etc.
I started using a static method in the GetMessage() and GetDiscussion() methods to fix the IDs as the messages were returned to the controller by ordering the threads by the MessageID then iterating over them to change the values to 1,2,3,etc.
Now I've changed that to add a MessageNumber property, save the message to the database when a new message is created (using EF6), retrieve the message, get the count of messages, update MessageNumber to the count and save the changes.
The latter works but I assume could cause problems if a new message is created between saving the message then retrieving the count. Two messages would have the same MessageNumber.
Is there a better way to do this or a should I use a different way to access the messages that doesn't need the ID?

The id is just the key for the table; it's not really intended to be part of the UI, even though you commonly see ids floating around in URLs across the web. It's far better to expose and use something like a slug for user-facing scenarios.
Regardless, though, what you're trying to do is really not possible. The id is typically set as an identity column, and is auto-incremented for each row in the table. Even if you don't rely on auto-increment and set it manually, you still need to ensure a unique value for each one (i.e., you can't repeat id 1 for multiple rows. The only way around this would be to create a composite key utilizing a manually set id and something like the foreign key to the Discussions table, but that's really, really, not a good thing to do. Please, don't do that. Not only would any good DBA smack you for using a foreign key as part of a composite key for another table, but then you have a ton manual work to do each time you want to save a new message.
My best advice is to just not worry about the id. If you want a consecutive number, you can create another property much like the MessageNumber property you have already and put anything you want in that as long as it's not a key or index for the table. That means you can't (or at least shouldn't) actually retrieve anything using that field. You would still need to lookup by the actual row id, or something like a slug, as mentioned earlier.

I'm not sure why you want to do what you want to do, but if your implementation of Messages is a List, then you can use an index and add one to it. An indexes are by nature consecutive numbers. You would do that something like this:
int index = Messages.FindIndex(message => message.MessageID = theID);
If you want something a little more flexible:
Messages.Select((m, index) => new { index, Message = m })
.Single(message => message.MessageID == theID);

Related

RavenDB Relational Model

I'm working on a side project to create a Forum built on top of RavenDB. I am currently trying to work out the relationship between Authors of Topics, and the "Last Reply" user on a topic. In a typical relational model I would simply store the FK to the User who posted the topic, and have a join off to the replies table to get the most recent replies author. This is obviously not the use case for Raven or any Document store for that matter.
What would be the most "optimal" way of pulling this off? Currently I'm tossing around a couple ideas.
Idea 1:
Save the FK of the Author on the Topic model, add a JsonIgnored User object that I will populate on the Topic load by using an Include in my session load (so one request so far from the client side, just makes the Load itself and model a bit complicated). Then possibly using a map-reduce index to get the most recent replies author (or even the same method as getting the Topic Author, so 1 or 2 queries depending).
Idea 2:
Saving both the Author and the most recent reply User on the model. Main "problem" here is the potential for stale data (say if a username changes). However that could potentially be alleviated with a background task (or simply keeping that in mind when updating a user document and going back over all posts from a user).
Example of the models in question.
public class User
{
public string Id { get; set; }
public string UserName { get; set; }
public string PasswordHash { get; set; }
}
public class Topic
{
public string Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
// Idea 1 Relationships
public string AuthorId { get; set; }
[JsonIgnore]
public User Author { get; set; } // Would need to be populated on loads from an Include on AuthorId
public string MostRecentReplyUserId { get; set; }
[JsonIgnore]
public User MostRecentReplyUser { get; set; } // Same as Author
// Idea 2 Relationships
public User Author { get; set; }
public User MostRecentReplyUser { get; set; }
}
Note: I would likely add a method to the User model to return a "clean" version where I scrub out things like the PasswordHash and use that on the Save for Idea 2.
Depending on your needs in case of update and query performance both ways may be the better choice.
I personally would recommend the first idea because you don't need to update existing documents when some data changes on the user records. Using include on query/load time is quite a nice feature of ravendb which can help you when retrieving nested records from the database.
Just make sure that you don't forget to include all nested documents - otherwise you may get many roundtrips.
Embedding documents (like Idea 1 but with a stored value of the users) may be better if your data processing is seperated from the data retrieval and you don't have access to the database session when converting data to be handed out to the frontend. We're using such a system that heavily relies on that (getting one input and mapping out a json pendant of the value) - this seperates data retrieval logic completely from the output (like mapping to json) logic.
Downside here: You've to make sure that existing (embedded) data get's updated whenever a user changes and the data that is transferred over the wire is more than on idea 1.

Query over a collection of composite keys in RavenDB using C#

I have a collection in RavenDB of this class...
public class Report
{
public string User { get; set; }
public int Quarter { get; set; }
public int Year { get; set; }
public string ReportData { get; set; }
}
There is only one report per quarter, per year for each user (so the identifying key is { User, Quarter, Year }. I want to create a function to save a list of these Reports, overwriting old ones or inserting new ones as needed. I came up with this:
public void Save(IList<Report> reports)
{
session.Query<Report>()
.Join(reports,
x => new { x.User, x.Quarter, x.Year },
y => new { y.User, y.Quarter, y.Year },
(x, y) => new { OldReport = x, NewReport = y })
.ForEach(report =>
{
if (report.OldReport != null)
report.OldReport.InjectFrom(report.NewReport);
else
session.Store(report.NewReport);
});
session.SaveChanges();
}
However, RavenDB does not support the .Join operator. Edit: I just realized that this also needs to be a right-outer-join for this to work, but I think it communicated my intent. I know I need to do some sort of Map Reduce to make this happen, but I'm new to RavenDB I can't find any good examples relevant to what I am doing. Has anyone tried something like this?
P.S. The .InjectFrom() operation is using Omu.ValueInjecter, if anyone was wondering.
There are multiple ways to do this, but the easiest way would be to provide your own document key instead of using the one Raven generates. This is often referred to as using "structured" or "semantic" keys. Here is a good description of the general technique.
Simply add a string Id property to your class. You want the document key to reflect the unique key you described, so probably it should have a value such as "reports/2013/Q1/bob" (but you might want a more unique value for user).
You can let .Net construct the key for you in the property getter, such as:
public class Report
{
public string Id
{
get { return string.Format("reports/{0}/Q{1}/{2}", Year, Quarter, User); }
}
public string User { get; set; }
public int Quarter { get; set; }
public int Year { get; set; }
public string ReportData { get; set; }
}
Now when you store these documents, you simply store them:
foreach (var report in reports)
session.Store(report);
If there is already a document with the same key, it will be overwritten with your new data. Otherwise, a new document will be written.
If you can't manipulate the document key, other techniques you could look into are:
You could run a query to delete any documents matching your changed data first. Then you could insert all of the data. But getting the query right will be difficult since there are multiple fields to match on. It is possible, but the technique is challenging.
You could use the Patching API to manipulate the data of the document already stored. Although you would still have to query to figure out which are new inserts and which are updates. Also, the patch would have to be tested against your entire database, so it would be slow.
I'm sure there are a few other ideas, but your safest and easiest bet is to go with semantic keys for the reports.

How to save an entity's change history

I have a Task entity with various navigation properties, including a Comment entity:
public Comment {
public int ID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime CreateDate { get; set; }
public DateTime LastEditedDate { get; set; }
public virtual User User { get; set; }
}
A comment can be edited, and I want to keep a history of all such changes. Think of the way Stackoverflow allows you to edit your questions/comments, but keeps a history of changes.
Keeping a history means more complexity, and it becomes harder to maintain. My options:
add properties such as public virtual ICollection<string> DescriptionHistory { get; set; }, and a similar one for Title, User, EditDate, etc. It gets out of hand very quickly.
keep the Title, Description, etc. properties as strings, but make them CSV strings and stuff all those changes into a fewer set of properties. This means more processing, but the entity is simpler. The problem is that it becomes tricky to associate one CSV fragment with the corresponding one from a different property, eg a historical title must match its historical description and date.
do both, have the current set of properties, and another single set of nullables like TitleHistory, DescriptionHistory, etc., which are CSV strings of older versions, and so it only gets complicated when you are dealing with the historical stuff.
Also there are problems around the storage of the user, unless I use a CSV of IDs rather than the entities.
What is the best approach to this problem? There are various techniques such as sprocs and "insert only" tables - but I am using Entity Framework 5, and so prefer a solution which leverages the technology I am already using.
I've settled on using an "insert only" table. Pity it's not easy to do with EF5.

History tables in .NET MVC code first approach?

I need to track a change history of some database objects in a MVC .NET application using the code first approach.
Here is what is meant by history table:
http://database-programmer.blogspot.de/2008/07/history-tables.html
I would use a history table for it, if I would write the SQL queries myself. But in the code first approach the SQL is generated... and I would like to stick to this paradigm.
The goal is a structure that holds all "old" revisions of changed/deleted entries together with some additional information (e.g. timestamp, user who changed it, ...)
Any ideas?
Regards,
Stefan
To be more specific - here is some code example:
public class Node {
public int NodeID { get; set; }
public string? data { get; set; } // sample data
}
public class NodeHistory {
public int NodeID { get; set; }
public string? data { get; set; }
public int UserID { get; set; }
public DataTime timestamp { get; set; }
}
What I need is some "framework" assistance to be able to add an entry to NodeHistory whenever a change is -persisted- to table the Node structure.
That means: Just overriding the set-method isn't a solution, as it would also create an entry, if the change to a "Node" is not persisted at the end (e.g. roleback).
I think the best approach for me would be to use a repository pattern and do the insertion into the NodeHistory table on every operation on the Node object that you see fit to keep a history of.
EDIT: Some code
public class NodeRepository{
public Node EditNode(Node toEdit, int userId){
using(new TransactionScope())
{
//Edit Node in NodeContext like you would anyway without repository
NodeContext.NodeHistories.Add(new NodeHistory(){//initialise NodeHistory stuff here)
NodeContext.SaveChagnes();
}
}
}
public class NodeContext:DbContext{
public DbSet<Node> Nodes{get;set;}
public DbSet<NodeHistory> NodeHistories{get;set;}
}
If you are looking for something simpler than this, then I have no idea what it might be.
This is really something you should do with a trigger. Yes, you have to write some sql for it, but then history is updated no matter how the update occurs, either manually, or through some other means.

Modifying specific item in nested collection in RavenDB

I have something that looks like the following document structure:
public class Document {
public int Id { get; set; }
public string Name { get; set; }
public List<Property> Properties { get; set; }
}
public class Property {
public int Id { get; set; }
public string Name { get; set; }
}
Now, querying and modifying Documents is easy. But I need to access specific Property-instances in my app, and it seems that they won't automatically get an ID like the root document does. And it seems this is by design in RavenDB.
I might be me stuck in the relational world, but what I'd like to do is basically retrieve the correct document, then get the right property, modify it and save the document again.
from property in document.Properties
where property.Id == someId
select property
...which will obviously not work as long as
RavenDB does not auto-set the Id field or
I don't make any ID-generating mechanism myself
Am I heading completely the wrong way, or does what I'm trying to do mak sense? Should I move the Properties out to being a root node and make some sort of reference to them in Document? Or should I just do something like this when inserting properties:
Retrieve the document with the list of properties
Get Properties[last]'s ID
Add 1 and insert new ID myself in new properties
?
This would, however, require at least two requests (one to get existing properties, one to save the changes) to the database, which just seems dirty and unnecessarsy for such a seemingly simple task.
I've found a lot of sortof similar posts, but none of them really answers this AFAIK.
Check to see how we do that in RaccoonBlog:
https://github.com/ayende/RaccoonBlog/blob/master/RaccoonBlog.Web/Infrastructure/Tasks/AddCommentTask.cs

Categories