I was wondering if someone could help me understand RavenDB transformations as I cant seem to get them working correctly. I tried following Ayende's post but I am not able to apply it to my situation. I have two models, a UserModel and an UserAddressModel. The code is as follows
public class UserModel
{
#region Properties
public string Id { get; set; }
public string AccountId { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public DateTime InsertDate { get; set; }
#endregion
}
and
public class UserAddressModel
{
public string Id { get; set; }
public string AccountId { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zipcode { get; set; }
public float Latitude { get; set; }
public float Longitude { get; set; }
public DateTime InsertDate { get; set; }
}
I have my RavenDB Index set up like such.
public class UserDashboard_ByName : AbstractIndexCreationTask<UserModel>
{
public UserDashboard_ByName()
{
Map = users => from user in users
select new { user.UserName, user.AccountId };
TransformResults =
(database, users) => from user in users
let useraddress = database.Load<UserAddressModel>(user.AccountId)
select new
{ FirstName = user.FirstName,
LastName = user.LastName,
Address1 = useraddress.Address1,
Address2 = useraddress.Address2
};
}
}
and I am calling it with the following code
using (var session = DataDocumentStore.Instance.OpenSession())
{
//Get the AccountId
var userDashboard =
(from user in session.Query<UserModel, UserDashboard_ByName>()
where user.UserName == userName
select user).SingleOrDefault();
}
When I call the index, it returns a UserModel type, not the anonymous type that I am expecting. Also, I do not understand how I can call
let useraddress = database.Load<UserAddressModel>(user.AccountId)
when there is no specific relationship that has been specified in code or anywhere else.
Maybe someone can explain to me how I should be joining data in RavenDB? Some expanded documentation or a nudge in the right direction to understanding this better would be greatly appreciated. Thanks in advance.
The problem is inside your TransformResult function where you call database.Load<UserAddressModel>(user.AccountId). You can only load the UserAddressModel with its id, not by a property like AccountId.
You have to store the id of the UserAddressModel inside your UserModel if you want to do it that way. However, I wouldn't model it in that way. UserAddressModel doesn't have any meaning on itself, so it should probably be part of UserModel. As a rule of thumb, you generally want to have your aggregrate roots as documents and everything else inside them.
You need to create another model (a view model basically), such as UserWithAddressModel that includes the 4 properties you wish to return:
public class UserWithAddressModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
}
Then do your query like this:
var userDashboard =
(from user in session.Query<UserModel, UserDashboard_ByName>()
where user.UserName == userName
select user).As<UserWithAddressModel>().SingleOrDefault();
I think this is what you are looking for.
UPDATE:
Try like this:
var userDashboard = session.Query<UserModel, UserDashboard_ByName>()
.Where(x => x.UserName == userName)
.As<UserWithAddressModel>()
.SingleOrDefault();
UPDATE2:
Sorry, just looked again at what you are trying to do, it won't work like that. I thought you had a reference to your UserAddressModel in your UserModel (such as UserAddressId).
Why are you splitting the 2 models up in the first place if they are a 1:1 relation?
Related
I am new to Entity Framework and Asp.NET, and therefore, struggling with creating database relationships within the Entity Framework.
I have two SQLite tables (Ticket and User) and have setup my entity models as follows:
public class Users
{
[ForeignKey("id")]
public int id { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public string email { get; set; }
public virtual ICollection<Tickets> Tickets { get; set; }
}
public class Tickets
{
public int id { get; set; }
public string summary { get; set; }
public string description { get; set; }
public string c_location { get; set; }
public string c_store_device { get; set; }
public string category { get; set; }
public DateTime? created_at { get; set; }
public DateTime? closed_at { get; set; }
public int priority { get; set; }
public int? assigned_to { get; set; }
public DateTime? due_at { get; set; }
public DateTime? updated_at { get; set; }
public string status { get; set; }
public virtual Users Users { get; set; }
}
I am trying to use Entity Framework 7 to export an IEnumerable<Tickets> that includes the User assigned to each Ticket.
I have tried to create my model relationship in MyDBContext as a single User can have multiple Tickets, and also has a foreign key associated in my Sqlite database (Tickets.assigned_to = User.id):
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Users - > many Tickets
modelBuilder.Entity<Users>()
.HasMany(p => p.Tickets)
.WithOne(e => e.Users)
.HasForeignKey(p => p.assigned_to);
}
My result ends up with Ticket data being exported, but against every ticket I see a null value for User:
[{"id":10002,...,"Users":null}]
When I use .Include() within my Repository to include each User like this:
public IEnumerable<Tickets> GetAll()
{
return _db.Tickets.Include(t => t.Users).ToList();
}
It results in the error
HTTP Error 502.3 - Bad Gateway
The specified CGI application encountered an error and the server terminated the process.
What I'm trying to retrieve is data that looks like:
{"Ticket";[{"id":10002,..."status":"closed"}],"Users":[{"id":"1"..."email":"johndoe#someplace.com"}]}
I know it probably has something to do with my relationship model, but I cannot work out what I am doing wrong.
First you should really derive your Users from IdentityUser. It helps when trying to wire up the relationship, but I will give you the answer based on your current models. Your ForeignKey property should be on the child entity. By naming conventions, which is what EF uses by default, your public Users Users works better if you put a public int UsersId. Then essentially what EF will do is from your public Users Users it will go to the Users table. Then it looks for the ForeignKey which is set to Id, so now we are in the Users Table looking at the id property. Then it looks for the naming convention UsersId and if it sees it, it will set that property to the value that it saw from the Users Table Id column.
Try using this
public class Users
{
public int id { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public string email { get; set; }
public virtual ICollection<Tickets> Tickets { get; set; }
}
public class Tickets
{
public int id { get; set; }
public string summary { get; set; }
public string description { get; set; }
public string c_location { get; set; }
public string c_store_device { get; set; }
public string category { get; set; }
public DateTime? created_at { get; set; }
public DateTime? closed_at { get; set; }
public int priority { get; set; }
public DateTime? due_at { get; set; }
public DateTime? updated_at { get; set; }
public string status { get; set; }
[ForeignKey("Id")]
public int UsersId { get; set; }
public virtual Users Users { get; set; }
}
and for your Fluent API configuring
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Users - > many Tickets
modelBuilder.Entity<Users>()
.HasMany(p => p.Tickets)
.WithOne();
}
Now all that does is create the relationship. In order to view the specific items you want to view, use a ViewModel. So, pull the two lists you want from where you want. Then use logic to separate the list how you want them to display.
public class UsersViewModel()
{
public UsersViewModel(Users user, List<Tickets> tickets)
{
this.first_name = user.first_name;
this.last_name = user.last_name;
this.email = user.email;
this.Tickets = new List<Tickets>();
foreach(var ticket in tickets)
{
if(ticket.UserId == user.Id)
{
this.Tickets.Add(ticket)
}
}
}
public string first_name { get; set; }
public string last_name { get; set; }
public string email { get; set; }
public List<Tickets> Tickets { get; set;}
}
then in your controller make your list
public IActionResult Index()
{
var usersList = _repository.Users.ToList();
var ticketsList = _repository.Tickets.ToList();
var model = new List<UsersViewModel>();
foreach(var user in usersList)
{
var listItem = new UsersViewModel(user, ticketsList);
model.Add(listItem);
}
return View(model);
}
or use a Linq query
public IActionResult Index()
{
var usersList = _repository.Users.ToList();
var model = new List<UsersViewModel>();
foreach(var user in usersList)
{
var ticketsList = from x in _repository.Tickets where x.UserId.Equals(user.Id) select x;
var listItem = new UsersViewModel(user, ticketsList);
model.Add(listItem);
}
return View(model);
}
then at the top of your view you should have
#model IEnumerable<UsersViewModel>
I have 3 tables that are connected together as follows:
Survey
public enum Language
{
Danish,
English
}
public class Survey
{
public Survey()
{
Id = Guid.NewGuid();
DateCreated = DateTime.Now;
Sort = 0;
}
[Key]
public Guid Id { get; set; }
public bool Active { get; set; }
public string Title { get; set; }
public bool Status { get; set; }
[Display(Name = "Kommentar")]
public string Comment { get; set; }
public string MAilReciever { get; set; }
public string CCMailReciever { get; set; }
public string BBCMailReciever { get; set; }
public int Sort { get; set; }
public DateTime DateCreated { get; set; }
public string StartText { get; set; }
public string EndText { get; set; }
public string RegardText { get; set; }
public Language Language { get; set; }
public virtual ICollection<UserSurvey> UserSurveys { get; set; }
public virtual ICollection<Chapters> Chapters { get; set; }
public virtual ICollection<Questions> Questions { get; set; }
}
ApplicationUser
The default ApplicationUser table from Mvc with identity.
UserSurvey (My relationShipTable between Survey and ApplicationUser)
public class UserSurvey
{
[Key, Column(Order = 0)]
public Guid SurveyId { get; set; }
[Key, Column(Order = 1)]
public string ApplicationUserId { get; set; }
public virtual Survey Survey { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
public bool Active { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string RegardText { get; set; }
}
I'm trying to add a new relation between a user and a servey by the following code:
UserSurvey Item = new UserSurvey();
Item.Survey = s;
Item.ApplicationUser = userinfo;
Item.Active = true;
Item.StartDate = Convert.ToDateTime(dateFrom);
Item.EndDate = Convert.ToDateTime(dateTo);
db.UserSurvey.Add(Item);
db.SaveChanges();
The error message I get says that I cant "Duplicate the SurveyId primary key because it already exist in the database (table survey).
I know it exist but I just want to make a relation between users and surveys, not create a new survey or user.
Thanks for your help
Your problem is that you are providing a completed UserSurvey object with nested objects (Survey/ApplicationUser) populated. When you do that and set the changeset as "Add" (caused by db.UserSurvey.Add(Item);). EF will attempt to issue INSERT INTO for the parent (UserSurvey) and all child/related(Survey/ApplicationUser) tables since it thinks that the "Add" changeset applies to all of these tables. To resolve your issue, you need to just provide the Ids. That will just insert a new relationship:
UserSurvey Item = new UserSurvey();
//Item.Survey = s; //This object filling is making EF believe that you are attempting to add a new record in Survey. Comment it out
//Item.ApplicationUser = userinfo; //This object filling is making EF believe that you are attempting to add a new record in ApplicationUser. Comment it out
Item.ApplicationUserId = userinfo.ApplicationUserId;//or whatever is the id column name in ApplicationUser table/class
Item.SurveyId = s.SurveyId;//I see that you are missing SurveyId property in UserSurvey. You will need to add this. This will be the Foreign Key to Survey table just like you have ApplicationUserId FK to ApplicationUser table.
Item.Active = true;
Item.StartDate = Convert.ToDateTime(dateFrom);
Item.EndDate = Convert.ToDateTime(dateTo);
db.UserSurvey.Add(Item);
db.SaveChanges();
So, the underlying idea is to fill the Ids if the nested records (Survey/Application in this case) already exist. You only populated nested object when you want EF to attempt to INSERT them too for you. Dont populate them if you dont want this. Just the Ids help EF to create just the relationship and not go after creating these related records again for you.
Hope this helps.
I have created a form which displays the list of users. When an item on the list is clicked, the belonging properties have to be passed to controller. In order to do so, I have added the following ActionLink:
#Html.ActionLink(#item.Username.ToString(), "UserEdit", "Admin", new DemoRes.Models.User{ UserId = item.UserId, Email= item.Email, Username=item.Username, Password=item.Password, IsActive=item.IsActive, Ownership=item.Ownership}, null)
I have checked whether the data is passed corrctly to view and it seems OK:
item.Ownership
Count = 1
[0]: 18878
item.Ownership.GetType()
{Name = "List`1" FullName = "System.Collections.Generic.List`1[[System.Int64, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}
Then, when the object is passed to the following UserEdit method in controller, all the properties are set correctly but Ownership list is empty:
public ActionResult UserEdit(DemoRes.Models.User user)
{
//here user.Ownership is empty
}
and this is User class:
public class User
{
public User()
{
}
[BsonId(IdGenerator = typeof(CombGuidGenerator))]
public Guid UserId { get; set; }
[BsonElement("Username")]
public string Username { get; set; }
[BsonElement("Password")]
public string Password { get; set; }
[BsonElement("Role")]
public int Role { get; set; }
[BsonElement("Email")]
public string Email { get; set; }
[BsonElement("FirstName")]
public string FirstName { get; set; }
[BsonElement("LastName")]
public string LastName { get; set; }
[BsonElement("AuthLevel")]
public int AuthLevel { get; set; }
[BsonElement("RememberMe")]
public bool RememberMe { get; set; }
[BsonElement("IsActive")]
public bool IsActive { get; set; }
[BsonElement("Note")]
public string Note { get; set; }
[BsonElement("Ownership")]
public List<long> Ownership { get; set; }
}
Does anyone know what is the correct way to bind all class properties (primitives and complex types such as lists) correctly in order to pass them from view back to controller?
You cannot post whole model via single query string Ownership=item.Ownership. Post whole model by Form to action.
I'm using ASP.Net MVC 5 from Visual Studio. I want to create a user profile with complex types. I have modified the code of the User class in IdentityModels.cs file. Here's the code:
public class User : IUser
{
public User()
: this(String.Empty)
{
}
public User(string userName)
{
UserName = userName;
Id = Guid.NewGuid().ToString();
}
[Key]
public string Id { get; set; }
public string UserName { get; set; }
public string Phone { get; set; }
public string MobilePhone { get; set; }
public string Email { get; set; }
public string Address { get; set; }
}
I've also changed the views for this model. This works great without any problems. However, if I change the type of the Address property above to Address, meaning: public Address Address { get; set; } it fails.
I've tried using the virtual keyword for it but it didn't work. Please note that every time I create the database tables from scratch. Also, I checked the database and the information is inserted into database with correct foreign keys but I don't know what the problem is.
The code execution fails in the code below in the line await Users.Create(user) which returns false:
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
try
{
// Create a profile, password, and link the local login before signing in the user
User user = new User(model.UserName)
{
UserAddress = model.Address,
Email = model.Email,
Phone = model.Phone,
MobilePhone = model.MobilePhone
};
if (await Users.Create(user) &&
await Secrets.Create(new UserSecret(model.UserName, model.Password)) &&
await Logins.Add(new UserLogin(user.Id, IdentityConfig.LocalLoginProvider, model.UserName)))
{
await SignIn(user.Id, isPersistent: false);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError(String.Empty, "Failed to create login for: " + model.UserName);
}
}
catch (DbEntityValidationException e)
{
ModelState.AddModelError("", e.EntityValidationErrors.First().ValidationErrors.First().ErrorMessage);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Update:
Here's the Address class in case you wonder:
public class Address
{
public int ID { get; set; }
[Required]
public string Country { get; set; }
[Required]
public string City { get; set; }
[Required]
public string Street { get; set; }
[Required]
public string PostalCode { get; set; }
}
Update 2
Here's the image of the entered data:
It seems that this is a bug. I won't select this as the answer until it is absolutely proven. However when I change my controller's code from:
if (await Users.Create(user) &&
await Secrets.Create...
to:
await Users.Create(user);
if (await Secrets.Create...
it works without any problems. Seems to me that this should be a bug since I can load and edit the data perfectly.
This means that for some reason, even if the IUserStore.Create succeeds, it returns false in case the model is a complex type.
The problem that you have here (at least specific to changing Address to be an object rather than a string), is that your models aren't built correctly to relate them properly. Here is what you should be looking at.
public class User : IUser
{
public User()
: this(String.Empty)
{
}
public User(string userName)
{
UserName = userName;
Id = Guid.NewGuid().ToString();
}
[Key]
public string Id { get; set; }
public string UserName { get; set; }
public string Phone { get; set; }
public string MobilePhone { get; set; }
public string Email { get; set; }
// This FK doesn't need to explicitly be declared, but I do so as it helps me
// with the understanding of my structure a bit better.
public int AddressId { get; set; }
public Address Address { get; set; }
}
You also need to relate your Address back to your User class. I'm not sure how you want to do that, but, assuming that multiple people can live at the same address, you'll want a one-to-many relationship. (Right now, you receive an error because you don't specify the relationship.)
You have to do this in the Address model:
public class Address
{
[Key]
public int ID { get; set; }
[Required]
public string Country { get; set; }
[Required]
public string City { get; set; }
[Required]
public string Street { get; set; }
[Required]
public string PostalCode { get; set; }
// I would give this a better property name than "Users" but just putting
// this here for now.
public virtual ICollection<User> Users { get; set; }
}
This way, when your database builds, Entity Framework can now properly build the relationships (where, before, it couldn't tell what you intended - hence the error when you switch over to Address).
Of course, there may be other issues, but, this is one that would cause problems.
I am developing an ASP.NET MVC4 application with EF Code First. I am having a many-to-many relationship among following classes. I have defined relationship using EF fluent api in my context class. But I am getting an error as it is trying to insert values into one of my master table involved in many-to-many relation. Can anyone help me correct my problem. Thanks in advance and for valuable time.I am using repository pattern and unit of work with Ninject as dependency injection.
Participant Class
public class Participant
{
[Key]
public int Id { get; set; }
[DisplayName("First Name")]
[StringLength(50, ErrorMessage = "First name cannot be more than 50 characters")]
[Required(ErrorMessage = "You must fill in first name")]
public string FirstName { get; set; }
[DisplayName("Last Name")]
[StringLength(50, ErrorMessage = "Last name cannot be more than 50 characters")]
[Required(ErrorMessage = "You must fill in last name")]
public string LastName { get; set; }
[Required(ErrorMessage = "You must indicate your full birthday")]
[DisplayName("Birthday")]
[DataType(DataType.DateTime)]
public DateTime BirthDate { get; set; }
[DisplayName("Gender")]
[Required(ErrorMessage = "You must select gender")]
public int Gender { get; set; }
public string Address { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
[DisplayName("Zip code")]
[StringLength(10, ErrorMessage = "Zip code cannot be more than 10 characters")]
public string ZipCode { get; set; }
public string Mobile { get; set; }
public string PhotoUrl { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public virtual ICollection<Interest> Interests { get; set; }
}
Interest Class
public class Interest
{
public int Id { get; set; }
public string InterestName { get; set; }
public virtual ICollection<Participant> Participants { get; set; }
}
DataContext
public class STNDataContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Participant> Participants { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<Interest> Interests { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<SecurityQuestion> SecurityQuestions { get; set; }
public DbSet<Tour> Tours { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Participant>().
HasMany(p => p.Interests).
WithMany(i => i.Participants).
Map(
m =>
{
m.ToTable("ParticipantInterests");
m.MapLeftKey("ParticipantId");
m.MapRightKey("InterestId");
});
modelBuilder.Entity<User>().HasRequired(u => u.Role);
modelBuilder.Entity<Participant>().HasRequired(p => p.Country);
}
public virtual void Commit()
{
base.SaveChanges();
}
}
Controller Code
public virtual ActionResult Register(StudentRegisterViewModel studentRegisterViewModel)
{
if (ModelState.IsValid)
{
if (_userService.IsUserExists(studentRegisterViewModel.Participant.User) == false)
{
// Attempt to register the user
//WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
//WebSecurity.Login(model.UserName, model.Password);
studentRegisterViewModel.Participant.User.Username = studentRegisterViewModel.Username;
studentRegisterViewModel.Participant.User.Email = studentRegisterViewModel.Email;
studentRegisterViewModel.Participant.User.DateCreated = DateTime.Now;
studentRegisterViewModel.Participant.User.Id = 3;
studentRegisterViewModel.Participant.User.IsApproved = false;
studentRegisterViewModel.Participant.User.RoleId = 2;
studentRegisterViewModel.Participant.CountryId = 1;
var participant = new Participant
{
Id = studentRegisterViewModel.Participant.Id,
FirstName = studentRegisterViewModel.Participant.FirstName,
LastName = studentRegisterViewModel.Participant.LastName,
Interests = new Collection<Interest>()
};
var interests = new List<Interest>();
foreach (var interestItem in studentRegisterViewModel.SelectedInterests)
{
var interest = new Interest { Id = interestItem, Participants = new Collection<Participant>() };
interest.Participants.Add(participant);
interests.Add(interest);
}
studentRegisterViewModel.Participant.Interests = interests;
_participantService.CreatParticipant(studentRegisterViewModel.Participant);
//_userService.CreatUser(studentRegisterViewModel.Participant.User);
//TODO: Need to check if do we need to register the user and get him signed-in. If yes signing in implementation goes here.
var user = _userService.GetUser(studentRegisterViewModel.Participant.User.Username);
//Session["User"] = user;
//FormsAuthentication.SetAuthCookie(user.Username, false);
//Growl("Welcome", "Thanks for registering and welcome to Truck Tracker.");
//return RedirectToAction("Index", "Home");
}
}
//return RedirectToAction("Index", "Home");
// If we got this far, something failed, redisplay form
studentRegisterViewModel.Gender =
Enum.GetNames(typeof(Gender)).Select(
x => new KeyValuePair<string, string>(x, x.ToString(CultureInfo.InvariantCulture)));
studentRegisterViewModel.Interests = _interestService.GetAllInterests();
return View(studentRegisterViewModel);
}
Ideally it should insert Participant into Participants Table and Participant Interests in ParticipantInterests many-to-many table. But it is giving following error
{"Cannot insert the value NULL into column 'InterestName', table 'StudyTourNetworkDB.dbo.Interests'; column does not allow nulls. INSERT fails.\r\nThe statement has been terminated."}
It is trying to insert into Interests table which should not happen.
Participant ParticipantInterests Interests
Id Id Id
FirstName ParticipantId InterestName
LastName InterestId
This is how the tables in the database. The Interest table has a fixed set of records(Study, Job, Other,etc) which get displayed in Interested In dropdown. The registering participant can select multiple interested in options and when he clicks Sign Up button the Participant record will get saved in Participant Table and selected Interests in ParticipantInterests table.
Thanks
I was messing around with the same things the other day. Pain to get it figured out but here is a very basic example of a many to many I got working:
namespace Project.Models
{
public class Affiliate
{
public Affiliate()
{
Merchants = new HashSet<Merchant>();
}
public int Id { get; set; }
public ICollection<Merchant> Merchants { get; set; }
}
}
namespace Project.Models
{
public class Merchant
{
public Merchant()
{
Affiliates = new HashSet<Affiliate>();
}
public int Id { get; set; }
public ICollection<Affiliate> Affiliates{ get; set; }
}
}
and in the DbContext I did this:
modelBuilder.Entity<Merchant>().
HasMany(c => c.Affiliates).
WithMany(p => p.Merchants).
Map(
m =>
{
m.MapLeftKey("MerchantId");
m.MapRightKey("AffiliateId");
m.ToTable("MerchantAffiliates");
});
Update
I am trying to understand what you are looking to accomplish. It seems like this:
A participant can be interested in many things. Multiple participants can be interested in the same thing. If that is the fact I would consider changing the model so a participant has a list of interests, but the interests table, in my opinion, does not need a list of participants. If you wanted to retrieve say all participants with an interest in hiking you could just do something like this:
Context.Participants.ToList().Where(x => x.Interest.Name == "hiking");
Make sense?