Raven DB - Have it Auto Generate its own Key - c#

I current have an object that has a public property called Id. When I store the object, I would like the Id to be part of the Data and not become the document Id like it currently does. When creating the document store I only set the connection string.
using (var session = documentStore.OpenSession())
{
session.Store(a);
session.SaveChanges();
}
a can be thought of an object as:
public class A
{
public Guid Id { get; set; }
//other properties
}
So either I want it to generate a random Id or have both the data and the documentId being the Id property of class A.
EDIT:
So two possibilities:
1. Document Id = Id and the
Data Section contains:
{
data //sorry if the notation is not correct, doing it off of memory
{
Id: [my guid]
//other properities
}
}
Or the data containing the Id and Document ID = randomly generated by raven

You can use this if you want Id to be a property that you control on your own and have raven use another property as the documents id instead:
public class User
{
public string DocumentId { get; set; }
public Guid Id { get; set; }
public string Name { get; set; }
public User()
{
Id = Guid.NewGuid();
}
}
documentStore.Conventions.FindIdentityProperty = prop =>
{
if (prop.DeclaringType == typeof(User))
return prop.Name == "DocumentId";
return prop.Name == "Id";
};

Related

Getting dynamic object instead of strong typed from MongoDB in .net core c#

I try to use MongoDB in combination with .net core (c#) to save some survey results.
The challenge is that I plan to make it as generic as possible to be able to add other rating controls later.
I am able to save different result types in one table.
public class VoteBase
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public User User { get; set; }
public Control Control { get; set; }
public string ControlType { get; set; }
}
public class VoteStarRatingControl : VoteBase
{
public int? Rating { get; set; }
}
public class VoteStarRatingWithComment : VoteStarRatingControl
{
public string Comment { get; set; }
}
I created a table of base type as MongoCollection:
private readonly IMongoCollection<VoteBase> _voteBases;
To save it to that collection I used this code (where the DTO is same content datatransfer object to decouple the REST service from DB structure):
List<VoteBase> dbVotes = new List<VoteBase>();
foreach (VoteBaseDTO v in votes)
{
switch (v) {
case VoteStarRatingWithCommentDTO src:
dbVotes.Add(new VoteStarRatingWithComment() { User = new User() { Id = UserId }, Control = new Control() { Id = src.ControlId }, Rating = src.Rating, Comment = src.Comment });
break;
case VoteStarRatingControlDTO sr:
dbVotes.Add(new VoteStarRatingControl() { User = new User() { Id = UserId }, Control = new Control() { Id = sr.ControlId }, Rating = sr.Rating });
break;
}
}
_voteBases.InsertMany(dbVotes);
return dbVotes;
Until here all works fine.
Now I try to get the votes back for a specific list of controls (for one survey).
The following command fails with
'Element 'Rating' does not match any field or property of class SurveyToolRestAPI.Models.VoteBase.'
object obj = _voteBases.Find(vb => vb.Control.Id == "5e9c24c50a099728b027e176").SingleOrDefault();
This is because it is of Type StarRatingControl instead of type VoteBase.
Is there a way to get the list as dynamic instead of strong typed from a MongoDB collection?
Thanks Eldho, you pointed me to the right direction.
Doings some more research with the Bson... attributes I found the solution finally in this post Mongodb collection as dynamic
The key is to add a BsonKnownTypes attribute. The it can be used as strong typed collection and the list will contain the appropriate subtypes.
[BsonKnownTypes(typeof(VoteBase), typeof(VoteStarRatingControl), typeof(VoteStarRatingWithComment))]

Dependency injection to choose between local and remote data

I need to create a model with dynamic choice of the source of data as a property of a model (code first from database. Code-second).
class MyModel{
public int MyModelId {get; set;}
...
public int PropertyId {get;set;}
public virtual Property Property {get;set;} // this what I need to choose.
...
The Property need to be get from Property table from a database if in a config file it's set <property>remote</property> and from a local container with the same structured as from the database if <property>local</property>.
class Property{
public int PropertyId {get;set}
public string name {get;set;}
public virtual ICollection<MyModel> MyModels {get;set;}
public Property()
{
MyModels = new List<Model>();
}
}
and the local data is like
List<Property> lProperty = new List<Property>()
{{PropertyId = 1,name = "val1"},
{PropertyId = 2,name = "val2"},
{PropertyId = 3,name = "val3"} ...}
At this case you should refuse from EF relationship and write something like this:
public class MyModel
{
public int MyModelId { get; set; }
//PropertyId now is not real FK to PropertyTable at DB it is just another column,
//but it still will store reference to Property
public int? PropertyId { get; set; }
[NotMapped]
public Property Property
{
get
{
if (PropertyId.HasValue)
{
if (ForExampleStaticClass.config("property") == "remote")
using (var context = new Context())
{
return context.PropertyTable.Where(x => x.PropertyId == PropertyId.Value).FirstOrDefault();
}
else
return ForExampleStaticClass.lProperty.Where(x => x.PropertyId == PropertyId.Value).FirstOrDefault();
}
else
return null;
}
set
{
//I consider that Property(value) already exists at DB and/or Local Storage.
PropertyId = value.PropertyId;
}
}
}

Load all documents from RavenDB

I have this model:
namespace Model
{
public class Category
{
public int Id { get; set; }
public string Name { get; private set; }
public Category()
{ }
public Category(string name)
{
Name = name;
}
}
}
When I store a document and retrieve it, the result is a list of documents with zero elements:
using (var session = _documentStore.OpenSession())
{
session.Store(category);
session.SaveChanges();
var categories = session.Query<Model.Category>().ToList();
}
category get the proper id, that is "1". But when I do the Query, then I do not get any elements.
If I Load the document:
var category = session.Load<Model.Document>("categories/1")
Instead of the Query, I get the proper category.
How do I load all documents from Raven?
I figured it out:
I have to wait for non staled results. So if I change my Query out with this:
session.Query<Model.Category>().Customize(cr => cr.WaitForNonStaleResults()).ToList();
it works just fine.

Can DbPropertyValues.SetValues be used in reverse to populate a DTO from an entity's current values?

Can the Entity Framework method DbPropertyValues.SetValues be used in reverse to set the values of an object, namely a view model or Data Transfer Object, based on the current values of properties within an entity?
This is one example of how I am using SetValues together with DTOs to update only the properties that I specify in the class definition for the DTO.
public Address UpdateAddress(Address address)
{
using (var context = new OrdersContext())
{
var targetAddress = context.Addresses.FirstOrDefault(a => a.AddressID == address.AddressID && a.User.UserName.ToLower() == HttpContext.Current.User.Identity.Name.ToLower());
if (targetAddress == null) return null; // Address not found or user doesn't own this address.
context.Entry(targetAddress).CurrentValues.SetValues(address);
context.SaveChanges();
return new Address
{
AddressID = targetAddress.AddressID,
AddressLine1 = targetAddress.AddressLine1,
AddressLine2 = targetAddress.AddressLine2,
City = targetAddress.City,
State = targetAddress.State,
ZipCode = targetAddress.ZipCode
};
}
}
public class Address
{
public int AddressID { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
// public string UserID { get; set; } // UserID is excluded from the view model so that it cannot be updated.
}
As the documention on SetValues indicates
The given object can be of any type. Any property on the object with a
name that matches a property name in the dictionary and can be read,
will be read. Other properties will be ignored. This allows, copying
of properties from simple Data Transfer Objects (DTOs).
Can SetValues be used in reverse to set the values of the DTO from the entity, so that the values don't have to be set manually one by one? Or is there another method that works like SetValues but in reverse?
There's another DbPropertyValues method called ToObject, but it doesn't work with DTOs.
I'd like to do something like this:
public Address UpdateAddress(Address address)
{
using (var context = new OrdersContext())
{
var targetAddress = context.Addresses.FirstOrDefault(a => a.AddressID == address.AddressID && a.User.UserName.ToLower() == HttpContext.Current.User.Identity.Name.ToLower());
if (targetAddress == null) return null; // Address not found or user doesn't own this address.
context.Entry(targetAddress).CurrentValues.SetValues(address);
context.SaveChanges();
return context.Entry(targetAddress).ToObject(new Address());
}
}
Thanks!

MongoDB: automatically generated IDs are zeroes

I'm using MongoDB and official C# driver 0.9
I'm just checking how embedding simple documents works.
There are 2 easy classes:
public class User
{
public ObjectId _id { get; set; }
public string Name { get; set; }
public IEnumerable<Address> Addresses { get;set; }
}
public class Address
{
public ObjectId _id { get; set; }
public string Street { get; set; }
public string House { get; set; }
}
I create a new user:
var user = new User
{
Name = "Sam",
Addresses = (new Address[] { new Address { House = "BIGHOUSE", Street = "BIGSTREET" } })
};
collection.Insert(user.ToBsonDocument());
The user is successfully saved, so is his address.
After typing
db.users.find()
in MongoDB shell, I got the following result:
{ "_id" : ObjectId("4e572f2a3a6c471d3868b81d"), "Name" : "Sam", "Addresses" : [
{
"_id" : ObjectId("000000000000000000000000"),
"Street" : "BIGSTREET",
"House" : "BIGHOUSE"
}
] }
Why is address' object id 0?
Doing queries with the address works though:
collection.FindOne(Query.EQ("Addresses.Street", streetName));
It returns the user "Sam".
It's not so much a bug as a case of unmet expectations. Only the top level _id is automatically assigned a value. Any embedded _ids should be assigned values by the client code (use ObjectId.GenerateNewId). It's also possible that you don't even need an ObjectId in the Address class (what is the purpose of it?).
Use BsonId attribute:
public class Address
{
[BsonId]
public string _id { get; set; }
public string Street { get; set; }
public string House { get; set; }
}
Identifying the Id field or property
To identify which field or property of
a class is the Id you can write:
public class MyClass {
[BsonId]
public string SomeProperty { get; set; }
}
Driver Tutorial
Edit
It's actually not working. I will check later why.
If you need get it work use following:
[Test]
public void Test()
{
var collection = Read.Database.GetCollection("test");
var user = new User
{
Name = "Sam",
Addresses = (new Address[] { new Address { House = "BIGHOUSE", Street = "BIGSTREET", _id = ObjectId.GenerateNewId().ToString() } })
};
collection.Insert(user.ToBsonDocument());
}
Get the collection as User type:
var collection = db.GetCollection<User>("users");
Initialize the field _id as follows:
var user = new User
{
_id = ObjectId.Empty,
Name = "Sam",
Addresses = (new Address[] { new Address { House = "BIGHOUSE", Street = "BIGSTREET" } })
};
Then you insert the object:
collection.InsertOne(user);
The _id field will automatically be generated.
In this link you will find alternative ways to have customized auto-generated ID(s).

Categories