Query over a collection of composite keys in RavenDB using C# - 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.

Related

Entity Framework database-first approach conditionally insert data

Using Entity Framework, how can I insert data if it does not exist, and update a field if it does?
public class Rootobject
{
public string odatacontext { get; set; }
public Value[] value { get; set; }
}
public class Value
{
public int AccountId { get; set; }
public DateTime? SubmissionDate { get; set; }
public string Status { get; set; }
}
To retrieve all the data from my API I use
root.value.Select(x => new satiaL
{
accountID = x.AccountID,
subDate = x.SubmissionDate,
x_status = x.Status
});
which of course will insert all records.
If the AccountID already exists in the database, I want to update the value of x_status, but if the AccountID does NOT yet exist in the database, then I want to insert all values.
You can not.
Upsert functionality is not part of an object/relational model - objects are there or not, and tracked by identity. Thre is no "update if it is not there" concept - at all. So, there is nothing for EfCore to implement.
This smells like abusing an ORM as a ETL loader, and this is not what you should do - ETL (mass data loading) is not what and ORM is made for. Time to write your own method to move data up into tables and possibly do upswert there. Did that years ago, comes really handy at times.
Right now all you can do is run a lot of finds for every account and basicalyl write code: create if not exists, update if exists.
Pseudocode:
var account = Find ( select ) or default from db
if account == null create
else update
savechanges
Something along this line. Beware of performance - you may want to just builk load all accounts. Beware of conflicting updates.

How to store a c# List of objects into ElasticSearch with NEST 2.x

I'm developing a cross-platform app with xamarin.forms and I'm trying to look for a way to store a List of Objects directly into ElasticSearch so I can later search for results based on the objects of the lists. My scenario is the folloring:
public class Box {
[String(Index = FieldIndexOption.NotAnalyzed)]
public string id { get; set; }
public List<Category> categories { get; set; }
}
public class Category {
[String(Index = FieldIndexOption.NotAnalyzed)]
public string id { get; set; }
public string name { get; set; }
}
My aim is to be able to search for all the boxes that have a specific category.
I have tried to map everything properly like it says in the documentation but if I do it like that, when I store a box, it only stores the first category.
Is there actually a way to do it or is it just not possible with NEST?
Any tips are very welcome!
Thanks
It should just work fine with AutoMap using the code in the documentation:
If the index does not exist:
var descriptor = new CreateIndexDescriptor("indexyouwant")
.Mappings(ms => ms
.Map<Box>(m => m.AutoMap())
);
and then call something like:
await client.CreateIndexAsync(descriptor).ConfigureAwait(false);
or, when not using async:
client.CreateIndex(descriptor);
If the index already exists
Then forget about creating the CreateIndexDescriptor part above and just call:
await client.MapAsync<Box>(m => m.Index("existingindexname").AutoMap()).ConfigureAwait(false);
or, when not using async:
client.Map<Box>(m => m.Index("existingindexname").AutoMap());
Once you succesfully created a mapping for a type, you can index the documents.
Is it possible that you first had just one category in a box and mapped that to the index (Before you made it a List)? Because then you have to manually edit the mapping I guess, for example in Sense.
I don't know if you already have important data in your index but you could also delete the whole index (the mapping will be deleted too) and try it again. But then you'll lose all the documents you already indexed at the whole index.

Consecutive IDs in a navigation property List

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);

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