MongoDB best practice for referencing - c#

I'm wondering what the best practice for modelling by using references would be given situation under. I'm using MongoRepository library.
public class User : Entity
{
publis string Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
public class Post : Entity
{
public string Id { get; set; }
public string Title { get; set; }
public string Summary { get; set; }
public DateTime Added { get; set; }
public User Owner { get; set; }
}
When storing the Post I want only reference to Owner (User) object instead of whole object underlying.
Currently I'm doing it like this, not knowing of better way...
var post = new Post
{
Title = "Example title",
Summary = "asd asd",
Added = DateTime.Now,
Owner = new User { Id = "someExistingUserId" }
};
postRepository.Update(post); //Save
..
//Then to get the post
var post = postRepository.GetById("previouslySavedPostId");
post.Owner = userRepository.GetById(post.Owner.Id);
return post;
userRepository and postRepository are of MongoRepository type.
Is this the correct approach to solving my problem using MongoDB with C#/MVC(4)?

You can use MongoDBRef object instead of User object.
public class Post : Entity
{
public string Id { get; set; }
public string Title { get; set; }
public string Summary { get; set; }
public DateTime Added { get; set; }
public MongoDBRef Owner { get; set; }
}
Then you can:
var mongo = new Mongo(config.BuildConfiguration());
mongo.Connect();
var DB = mongo.GetDatabase(_dataBaseName)
var post = new Post();
post.Owner = new MongoDBRef("User", userId); // First parameter is a mongoDB collection name and second is object id
// To fetch object referenced by DBRef you should do following
var owner = DB.FollowReference<User>(post.Owner);

Mongo is a document database and if you are used to using sql server it requires a slightly different way of thinking.
As you don't want the user password details in every single post, the way i would probably do it is to create a new class to contain any user info that might be required to display a post.
public class PostOwnerInfo
{
public string UserId { get; set; }
public string Name { get; set; }
}
Update your post entity, replacing the Owner property with an OwnerInfo property, of type PostOwnerInfo.
Then when you create a new post, do the following.
var user = userRepository.GetById(someExistingUserId);
var post = new Post
{
Title = "Example title",
Summary = "Example summary",
Added = DateTime.Now,
OwnerInfo = new PostOwnerInfo
{
UserId = user.Id,
Name = user.Name
}
};
postRepository.Update(post);
This way when you query for a post it will have all the user info that you require to display the post in it's OwnerInfo property with no further queries required.
var post = postRepository.GetById(previouslySavedPostId);
// post.OwnerInfo will contain user info
There is a certain amount of data redundancy, but in an document database this is how i would do it.
If you need the full user info for any reason just do a seperate query for it as you were doing before.
The idea is that you store all the user info you need for a post in a child document of the post, so you shouldn't need to do a seperate query for the user.
If the user data chages, just update the UserInfo field on all posts made by your user.
Your user data will rarely change, but you will query for posts very often.

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

Entity Framework Multiple Foreign Keys on same table not mapping correctly

I have seen several examples on this site of mapping multiple foreign keys on the same table to an entity. I think I have coded things the way they are shown on the examples, but when I try to save my entity, both foreign keys are pointing to the same entity rather than two separate ones. I am expecting that my parent entity would have reference to two different entities from the linked table. My classes are as follows:
public class User: PersistentEntity
{
[Required]
public string FullName { get; set; }
[Required]
public string HID { get; set; }
[Required]
public string VID { get; set; }
[Required]
[MaxLength(100)]
public string UserName { get; set; }
//nav properties
[InverseProperty("ImpersonatedUser")]
public virtual ICollection<UserOverride> ImpersonatedUsers { get; set; }
[InverseProperty("User")]
public virtual ICollection<UserOverride> Users { get; set; }
}
public class UserOverride: PersistentEntity
{
[Required]
[ForeignKey("User")]
public int UserId { get; set; }
[Required]
[ForeignKey("ImpersonatedUser")]
public int ImpersonatedUserId { get; set; }
[Required]
public bool Active { get; set; }
//nav fields
[IgnoreForValidation]
[ForeignKey("UserId")]
public virtual User User { get; set; }
[IgnoreForValidation]
[ForeignKey("ImpersonatedUserId")]
public virtual User ImpersonatedUser { get; set; }
}
I create several users without any ImpersonatedUser objects on them, then try to create a new User object with one of these previous users set as the ImpersonatedUser. I am setting the ImpersonatedUserId equal to the UserId from that user and adding this entity to the list of ImpersonatedUsers on User, but when I try to save, it changes the ImpersonatedUserId to the id of the new user I am saving, and adds this same user to the list of Users on the User object. If I try to set the entire ImpersonatedUseer object and then save, I get an error about multiplicity of foreign keys not being correct.
My question is, what am I doing wrong? this looks like what I have seen as other examples out here, but I can't get it to work properly. Do I have it modeled correctly? Thanks for any help.
EDIT---
//create a couple users
var user = new User
{
FullName = "Ritchie Blackmore",
HID = "01010101",
VID = "rblackmore",
UserName = "rblackmore"
};
var userResult = UserService.SaveOrUpdate(user);
here is how I am creating my user I am trying to save:
var impersonatedUsers = UserService.FindByReadOnly(u => u.UserName.Equals("rblackmore"));
var impersonatedUser = Queryable.FirstOrDefault(impersonatedUsers);
var user = new User
{
FullName = "Ronnie James Dio2",
HID = "03030303",
VID = "rjdio2",
UserName = "rjdio2",
Roles = new List<Role>
{
roleResult1.Entity, //pre-existing role
//TODO: the following are new roles, added while adding the user
//probably shouldn't allow this scenario, but it might come in handy
new Role
{
Name = "Client2",
Description = "Client",
DisplayName = "Client"
},
new Role
{
Name = "Developer2",
Description = "Developer",
DisplayName = "Developer"
}
},
ImpersonatedUsers = new List<UserOverride>
{
new UserOverride {ImpersonatedUserId = impersonatedUser.Id, SystemId = system.Id, Active = true}
}
};
var result = UserService.SaveOrUpdate(user);
As you can see, I am only setting the id of the impersonated user, not the entire impersonated user object. I have tried assigning the entire object as well, which threw an exception as it tried to change the key of the object to the new user's key.
Did you try just setting the Id and leaving the navigation properties alone?
Basically, when you say:
I am setting the ImpersonatedUserId equal to the UserId from that
user and adding this entity to the list of ImpersonatedUsers on User.
Leave the second part (after and) and just set the Id of User in ImpersonatedUserId property. That should create correct relationship.
Let me know how did it go.

how to put the list data obtained from database to another variable in c# mvc using petapoco

I have obtained the list of data from database in the following way
List<MakerCheckerModel> mkckdata = new List<MakerCheckerModel>();
var dataContext = new PetaPoco.Database("MessageEntity");
mkckdata = dataContext.Query<MakerCheckerModel>(PetaPoco.Sql.Builder.Append("Select * from MakerChecker1")).ToList();
The data comes in mkckdata. My model is of the following way.
public class MakerCheckerModel
{
public int MakerCheckerId { get; set; }
public string OldJson { get; set; }
public string NewJson { get; set; }
public string ModelName { get; set; }
}
Now I want to put the value obtained in OldJson and NewJson of mkckdata in new List type of model variables so that I can manipulate it further.I want something like this.
List<MakerCheckerModel> oldDataList = new List<MakerCheckerModel>();
oldDataList.Add(mkckdata.OldJson));
But this is not allowed here. PLease help me how to do this.

Reuse index transformer expressions fails on Id

I'm looking to be able to reuse some of the transform expressions from indexes so I can perform identical transformations in my service layer when the document is already available.
For example, whether it's by a query or by transforming an existing document at the service layer, I want to produce a ViewModel object with this shape:
public class ClientBrief
{
public int Id { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
// ellided
}
From this document model:
public class Client
{
public int Id { get; private set; }
public CompleteName Name { get; private set; }
public Dictionary<EmailAddressKey, EmailAddress> Emails { get; private set; }
// ellided
}
public class CompleteName
{
public string Title { get; set; }
public string GivenName { get; set; }
public string MiddleName { get; set; }
public string Initials { get; set; }
public string Surname { get; set; }
public string Suffix { get; set; }
public string FullName { get; set; }
}
public enum EmailAddressKey
{
EmailAddress1,
EmailAddress2,
EmailAddress3
}
public class EmailAddress
{
public string Address { get; set; }
public string Name { get; set; }
public string RoutingType { get; set; }
}
I have an expression to transform a full Client document to a ClientBrief view model:
static Expression<Func<IClientSideDatabase, Client, ClientBrief>> ClientBrief = (db, client) =>
new ClientBrief
{
Id = client.Id,
FullName = client.Name.FullName,
Email = client.Emails.Select(x => x.Value.Address).FirstOrDefault()
// ellided
};
This expression is then manipulated using an expression visitor so it can be used as the TransformResults property of an index (Client_Search) which, once it has been generated at application startup, has the following definition in Raven Studio:
Map:
docs.Clients.Select(client => new {
Query = new object[] {
client.Name.FullName,
client.Emails.SelectMany(x => x.Value.Address.Split(new char[] {
'#'
})) // ellided
}
})
(The Query field is analysed.)
Transform:
results.Select(result => new {
result = result,
client = Database.Load(result.Id.ToString())
}).Select(this0 => new {
Id = this0.client.__document_id,
FullName = this0.client.Name.FullName,
Email = DynamicEnumerable.FirstOrDefault(this0.client.Emails.Select(x => x.Value.Address))
})
However, the transformation expression used to create the index can then also be used in the service layer locally when I already have a Client document:
var brief = ClientBrief.Compile().Invoke(null, client);
It allows me to only have to have one piece of code that understands the mapping from Client to ClientBrief, whether that code is running in the database or the client app. It all seems to work ok, except the query results all have an Id of 0.
How can I get the Id property (integer) properly populated in the query?
I've read a number of similar questions here but none of the suggested answers seem to work. (Changing the Ids to strings from integers is not an option.)
I have a hard time following your sample fully, Really the best way to dig in to this would be with a failing self-contained unit test.
Nonetheless, let's see if I can pull out the important bits.
In the transform, you have two areas where you are working with the id:
...
client = Database.Load(result.Id.ToString())
...
Id = this0.client.__document_id,
...
The result.Id in the first line and the Id = in the second line are expected to be integers.
The Database.Load() expects a string document key and that is also what you see in __document_id.
The confusion comes from Raven's documentation, code, and examples all use the terms id and key interchangeably, but this is only true when you use string identifiers. When you use non-string identifiers, such as ints or guids, the id may be 123, but the document key is still clients/123.
So try changing your transform so it translates:
...
client = Database.Load("clients/" + result.Id)
...
Id = int.Parse(this0.client.__document_id.Split("/")[1]),
...
... or whatever the c# equivalent linq form would be.

Bind 2 table together (one to many relationship)

I am pretty new to c#, EF Code First and all that, so my question might be an easy one.
I am trying to create a very simple login. Each user must have a type (admin or client). How can I bind the usertype to my user table without generating a new type each time I insert a new user in the DB?
Here are my code first class:
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public bool Active { get; set; }
public UserType TypeId { get; set; }
}
public class UserType
{
public int Id { get; set; }
public string Type { get; set; }
}
public enum TypeEnum
{
Admin,
Client
}
When I launch the app, I want to be able to create 2 tables:
Users which is empty
UserTypes which must contain 2 types (Admin and Client).
Then, everytime I register a new user, so everytime I add a user to the Users table, I want to be able to use the UserTypes table.
When I launch the app, I want to be able to create 2 tables...
Not sure if I understand this correctly but you can seed initial data into the database with EF Code-First. An example how to do that is here:
Entity Framework Inserting Initial Data On Rebuild
If you really want to recreate the tables with every launch of your application you can use the DropCreateDatabaseAlways<T> initializer as mentioned in the example.
Then, everytime I register a new user, so everytime I add a user to
the Users table, I want to be able to use the UserTypes table.
For this you would load the existing UserType you want to assign from the database so that it is attached to the context and then create the new user:
using (var context = new MyContext())
{
var userType = context.UserTypes
.Single(u => u.Type == TypeEnum.Admin.ToString());
var newUser = new User { TypeId = userType, Username = ... etc. };
context.Users.Add(newUser);
context.SaveChanges();
}
Attaching to the context - by either loading from the database or calling Attach explicitely - makes sure that the UserType is not duplicated, i.e. no INSERT command will be sent to the database for the UserType.
With new version of EF (currently beyond the offical 4.1 version: Entity Framework June 2011 CTP) you can also do that:
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public bool Active { get; set; }
public UserType Type { get; set; }
}
public enum UserType
{
Admin = 1,
Client = 2,
}
The data will be saved as integer in your database but within your application you can use your enum like this:
var newUser = new User { Type = UserType.Admin, Username = ... };
context.Users.Add(newUser);
context.SaveChanges();

Categories