How to code ViewModel with Key for human-readable values instead of Ids - sql

I am getting an error when I perform an Update-Database in PM Console. I understand why, but given my needs I'm not sure how to best overcome it. I hope somebody can point me in the right direction.
I have 2 tables, one for countries and one for states. Usually I would use the Id for a primary key, but my client has asked that I make associations human readable. For example, instead of a user having a value for their country as 1, they want it to be "USA". Instead of a value of 14 for their state they way to see "CA" or "TX", etc.
My Migration Configuration.cs contains a seed routine to add countries and states.
When I run Update-Database, I get the following error:
Running Seed method.
System.Data.Entity.Infrastructure.DbUpdateException: Unable to
determine the principal end of the 'XXX_XXX_XXX.Models.State_Country'
relationship. Multiple added entities may have the same primary key.
---> System.Data.Entity.Core.UpdateException: Unable to determine the principal end of the 'XXXX_XXXX_XXXX.Models.State_Country'
relationship. Multiple added entities may have the same primary key.
My ViewModel contains:
namespace XXXX_XXXX_XXXX.Models
{
public class Country
{
[Key]
public string CountryCode { get; set; }
public int CountryId { get; set; }
public string CountryName { get; set; }
public virtual ICollection<State> States { get; set; }
}
public class State
{
[Key]
public string StateCode { get; set; }
public int StateId { get; set; }
public string StateName { get; set; }
[ForeignKey("Country")]
public string CountryCode { get; set; }
public virtual Country Country { get; set; }
}
}
My seed routine looks like this:
protected override void Seed(ProgramDbContext context)
{
// Initialize Countries
context.Countries.AddOrUpdate(c => c.CountryCode,
new Country { CountryId = 1,
CountryName = "United States",
CountryCode = "USA" });
context.Countries.AddOrUpdate(c => c.CountryCode,
new Country { CountryId = 2,
CountryName = "Canada",
CountryCode = "CAN" });
{ ... }
// Initialize States
context.States.AddOrUpdate(c => c.StateId,
new State { StateId = 1,
StateName = "Iowa",
StateCode = "IA",
CountryCode = "USA" });
context.States.AddOrUpdate(c => c.StateId,
new State { StateId = 2,
StateName = "Illinois",
StateCode = "IL",
CountryCode = "USA" });
context.States.AddOrUpdate(c => c.StateId,
new State { StateId = 3,
StateName = "California",
StateCode = "CA",
CountryCode = "USA" });
{ ... }
}
I DO understand that I am getting the error because CountryCode doesn't contain unique values for every column. If I set the CountryId to the Key and change the Foreign Key for States to CountryId I don't get the error, but then it seems I have to perform a lookup on the Id fields to get the CountryCode whenever I need it.
I also foresee that it's possible and even likely that the StateCode will eventually have a duplicate, though it doesn't currently.
Is it possible to have 2 [Key] attributes in a single table using ViewModel coding? I couldn't get that to work.
Do I need to place the [Key] attribute on the Id columns of each of these tables and then look-up the CountryCode and StateCode each time I need to update a user profile or create a dropdown list? Btw, I've created custom Identity fields for CountryCode and StateCode.
How should my ViewModel for Country and State be coded so that I can conveniently have my user profiles show "CA, USA" instead of 3, 1 - for example?
EDIT
#David offered to look at my View code for the Country and State dropdowns. I populate the State dropdown using jQuery
I populate Model.Countries in my controller with:
public IEnumerable<SelectListItem> GetCountryList()
{
var countries = new SelectList(dbLocations.Countries, "CountryCode", "CountryName").ToList();
countries.Insert(0, (new SelectListItem { Text = "Select Country", Value = "-1" }));
countries.Insert(1, (new SelectListItem { Text = "United Sates", Value = "54" }));
countries.Insert(2, (new SelectListItem { Text = "Canada", Value = "13" }));
countries.Insert(3, (new SelectListItem { Text = "Mexico", Value = "12" }));
countries.Insert(4, (new SelectListItem { Text = "Brazil", Value = "36" }));
countries.Insert(5, (new SelectListItem { Text = "------------------------", Value = "-1" }));
IEnumerable<SelectListItem> countrylist =
countries.Select(m => new SelectListItem()
{
Text = m.Text,
Value = m.Value
});
return countrylist;
}
An example of how I use in a View:
<div class="form-group">
#Html.LabelFor(m => m.CountryId, new { #class = "col-md-3 control-label" })
<div class="col-md-9">
#Html.DropDownListFor(
x => x.CountryId,
new SelectList(Model.Countries, "Value", "Text"),
new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.StateId, new { #class = "col-md-3 control-label" })
<div class="col-md-9">
<div id="stateid_select">
<select id="StateId" class="form-control" name="StateId"></select>
</div>
</div>
</div>
jQuery for Country change (retrieves states):
<script type="text/javascript">
// Populate State/Province dropdown on Country select
$(function () {
$('#CountryCode').change(function () {
$.getJSON('/Account/StateList/' + $('#CountryCode').val(), function (data) {
var cnt = 0;
var items = '<option>Select a State/Province</option>';
$.each(data, function (i, state) {
items += "<option value='" + state.Value + "'>" + state.Text + "</option>";
cnt = cnt + 1;
});
// Hide SELECT and show TEXT field if no state/provinces to choose from.
// Else show SELECT and hide TEXT field.
if (!cnt == 0) {
$('#stateprovince_select select').html(items).show().attr("disabled", false).removeClass("hide");
$('#stateprovince_text input[type="text"]').hide().attr("disabled", true).addClass("hide");
} else {
$('#stateprovince_select select').html('').hide().attr("disabled", true).addClass("hide");
$('#stateprovince_text input[type="text"]').show().attr("disabled", true).removeClass("hide");
$('#StateProvinceCode').hide();
}
});
});
});
</script>
jQuery calls this routine in Controller:
[AllowAnonymous]
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult StateList(int countryid)
{
var state = from s in dbLocations.States
where s.CountryId == countryid
select s;
return Json(new SelectList(state.ToArray(), "StateId", "StateName"), JsonRequestBehavior.AllowGet);
}
May be more than you needed to see. I made some changes to reflect the ModelView changes, though I haven't been able to run the app yet - still making changes. The jQuery needs some cleaning up, I know. ;)

You should leave the Key index on the Id column but add a unique constraint to the CountryCode. This has the following benefits:
You relationships are all using the integer Id columns which will perform better than joining on strings.
The CountryCode will now not allow duplicate entries.
The CountryCode can be shown to users and hides away the fact your database is linking with integers.
So now your models look like this:
public class Country
{
[Key]
public int Id { get; set; }
[Index("UX_Country_CountryCode", IsUnique = true)]
[MaxLength(10)]
public string CountryCode { get; set; }
public string CountryName { get; set; }
public virtual ICollection<State> States { get; set; }
}
public class State
{
[Key]
public int Id { get; set; }
[Index("UX_State_StateCode", IsUnique = true)]
[MaxLength(10)]
public string StateCode { get; set; }
public string StateName { get; set; }
[ForeignKey("Country")]
public int CountryId { get; set; }
public virtual Country Country { get; set; }
}
If you want to be able to have the same state code in different countries, extend the unique constraint like this:
public class State
{
[Key]
public int Id { get; set; }
[Index("UX_State_StateCodeCountryId", IsUnique = true, Order = 1)]
[MaxLength(10)]
public string StateCode { get; set; }
public string StateName { get; set; }
[ForeignKey("Country")]
[Index("UX_State_StateCodeCountryId", IsUnique = true, Order = 0)]
public int CountryId { get; set; }
public virtual Country Country { get; set; }
}

Related

Mapping problem when using Automapper in an Edit form with select list

I am trying to create an edit form which includes a selectlist that gets the data from the database. I am unable to display the form since I cannot map the viewmodel with the actual model using Automapper.
Contact.cs:
public int ContactId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string? EmailAddress { get; set; }
public int CompanyId { get; set; }
[ForeignKey("CompanyId")]
public Company Company { get; set; }
ContactEditViewModel.cs:
public int ContactId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string? EMailAddress { get; set; }
[Range(1, int.MaxValue, ErrorMessage = "Please select a company.")]
public int CompanyId { get; set; }
public SelectList? Company { get; set; }
Edit View
<div class="form-group">
<label asp-for="Company" class="control-label"></label>
<div class="input-group mb-3">
<select asp-for="CompanyId" class="form-select" asp-items="#Model.Company"></select>
</div>
</div>
ContactsController Edit Action:
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var contact = await _context.Contacts.FirstOrDefaultAsync(c => c.ContactId == id);
var model = new ContactEditVM
{
Company = new SelectList(_context.Companies, "CompanyId", "CompanyName"),
};
//var contact = mapper.Map<ContactEditVM>(await contactRepository.GetAsync(id));
mapper.Map(model, contact);
if (contact == null)
{
return NotFound();
}
//ViewData["CompanyId"] = new SelectList(_context.Companies, "CompanyId", "CompanyName", contact.Company);
return View(model);
}
MappingConfiguration
public class MapConfig : Profile
{
public MapConfig()
{
CreateMap<Contact, ContactListVM>().ReverseMap();
CreateMap<Contact, ContactCreateVM>().ReverseMap();
CreateMap<Contact, ContactEditVM>().ReverseMap();
}
}
The error I get is:
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types:
SelectList -> Company
Microsoft.AspNetCore.Mvc.Rendering.SelectList -> ENV.Data.Company
Destination Member:
Company
...
If I create a new instance of my viewmodel and assign values to it manually, without using Automapper, it works as intended. So what is wrong with my mapping?
Does it work if you outcomment the "Company" from your Contact.cs and outcomment the "Company" from your ContactEditViewModel.cs?
I think you need to define a mapping which tells autoMapper how to map a "SelectedList?" to a "Company".
For Example:
var autoMapperConfig = new MapperConfiguration(cfg =>
{
cfg.CreateMap<WalletData/*Source*/, BP_WalletDTO/*Destination*/>()
.ForMember(dest => dest.Id, memberOptions => memberOptions.MapFrom(src => src.Id))
.ForMember(dest => dest.Type, memberOptions => memberOptions.MapFrom(src => src.Type))
.ForMember(dest => dest.Attributes, memberOptions => memberOptions.MapFrom(src => new BP_WalletAttributesDTO
{
CryptocoinId = src.Attributes.Cryptocoin_id,
CryptocoinSymbol = src.Attributes.Cryptocoin_symbol,
Balance = src.Attributes.Balance,
IsDefault = src.Attributes.Is_default,
Name = src.Attributes.Name,
PendingTransactionsCount = src.Attributes.Pending_transactions_count,
Deleted = src.Attributes.Deleted,
IsIndex = src.Attributes.Is_index,
}));
});
Maybe this helps

Can I use an index as the source of an index in RavenDB

I'm trying to define an index in RavenDb that uses the output of another index as it's input but I can't get it to work.
I have the following entities & indexes defined.
SquadIndex produces the result I expect it to do but SquadSizeIndex doesn't even seem to execute.
Have I done something wrong or is this not supported?
class Country
{
public string Id { get; private set; }
public string Name { get; set; }
}
class Player
{
public string Id { get; private set; }
public string Name { get; set; }
public string CountryId { get; set; }
}
class Reference
{
public string Id { get; set; }
public string Name { get; set; }
}
class SquadIndex : AbstractIndexCreationTask<Player, SquadIndex.Result>
{
public SquadIndex()
{
Map = players => from player in players
let country = LoadDocument<Country>(player.CountryId)
select new Result
{
Country = new Reference
{
Id = country.Id,
Name = country.Name
},
Players = new[]
{
new Reference
{
Id = player.Id,
Name = player.Name
}
}
};
Reduce = results => from result in results
group result by result.Country
into g
select new Result
{
Country = g.Key,
Players = g.SelectMany(x => x.Players)
};
}
internal class Result
{
public Reference Country { get; set; }
public IEnumerable<Reference> Players { get; set; }
}
}
class SquadSizeIndex : AbstractIndexCreationTask<SquadIndex.Result, SquadSizeIndex.Result>
{
public SquadSizeIndex()
{
Map = squads => from squad in squads
select new Result
{
Country = squad.Country,
PlayerCount = squad.Players.Count()
};
Reduce = results => from result in results
group result by result.Country
into g
select new Result
{
Country = g.Key,
PlayerCount = g.Sum(x => x.PlayerCount)
};
}
internal class Result
{
public Reference Country { get; set; }
public int PlayerCount { get; set; }
}
}
No, you can't. The output of indexes are not documents to be indexed.
You can use the scripted index results to chain indexes, but that isn't trivial.

The ViewData item that has the key 'distic_id' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'

In my MVC project when run and I press edit option in in view at that time this error occur
The ViewData item that has the key 'distic_id' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'
In my view code
#Html.DropDownListFor(m => m.distic_id, Model.disticlist)
model is
public class city
{
public List<SelectListItem> disticlist { get; set; }
public int city_id { get; set; }
[Required]
[Display(Name = "enter the District name")]
public string city_name { get; set; }
[Required]
[Display(Name = "select district ")]
public int distic_id { get; set; }
}
if you want to get city or dist list in a drop down list please see the following code
1) Remove your code
2) Create one Model like this
3) if this drop down is used in more than one page CREATE ONE CONTROLLER like CommanController
4) write one method in this controller
See Below code
First need to create Model like this
public class Industry
{
public string Id { get; set; }
public string industryName { get; set; }
public string regexindustry { get; set; }
}
public class IndustryModel
{
public SelectList industryList { get; set; }
}
In Controller
Two Step 1 is Create one method it return type is List
and Call this method in any ActionReslut with the use of object
ViewBag.list=obj.getIndustryList();
public List<Industry> getIndustryList()
{
List<Industry> objindustry = new List<Industry>();
var connString = new SqlConnection(ConfigurationManager.ConnectionStrings["connectionString"].ConnectionString);
SqlCommand sqlComm = new SqlCommand("sp_selIndustryMaster", connString);
connString.Open();
sqlComm.CommandType = CommandType.StoredProcedure;
SqlDataReader sqldr = sqlComm.ExecuteReader();
int count = 0;
while (sqldr.Read())
{
if (count == 0)
{
objindustry.Add(new Industry { Id ="", industryName = "Select Industry" });
count++;
}
else
{
objindustry.Add(new Industry { Id = Convert.ToString(sqldr["industryCode"]), industryName = sqldr["subindustry"].ToString() });
}
}
return objindustry;
}
IN VIEW
#Html.DropDownListFor(model => model.txtindustry, new SelectList(ViewBag.List, "Id", "industryName", 0))
please use it your problem may be solve,

MVC4 Razor drop down list binding with foreign key

So as I wanted to have a deeper understanding. I added a little bit more functionality to the MSFT tutorial on MVC4 that you can find here (http://www.asp.net/mvc/tutorials/mvc-4/getting-started-with-aspnet-mvc4/intro-to-aspnet-mvc-4)
The model is very simple. You have movies and directors. Every movie has 1 director max.
I want the user to be able to assign a director to a movie from a drop down list and save it but somehow the movie gets saved with a null Director_ID field in the database.
Here are my simple models:
public class Movie
{
public int ID { get; set; }
[Required]
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
[Required]
public string Genre { get; set; }
public decimal Price { get; set; }
public string Ranking { get; set; }
public Director Director { get; set; }
}
public class Director
{
public int ID { get; set; }
public string Name { get; set; }
}
When the movie table gets generated it comes with a Director_ID field. Sweet!
I would like the user to select a director while editing a movie form a drop down list so
in the movie edit view I managed to bind a drop down list to a list of all directors obtained form the database
<div class="editor-field">
#Html.DropDownListFor(model => model.Director.ID, ViewBag.Directors as List<SelectListItem>, "All")
</div>
Controller:
//GET
public ActionResult Edit(int id = 0)
{
var DirectorsList = new List<SelectListItem>();
var DirQuery = from d in db.Directors select d;
foreach (var d in DirQuery)
{
DirectorsList.Add(new SelectListItem { Value = d.ID.ToString(), Text = d.Name });
}
ViewBag.Directors = DirectorsList;
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
I get my list of all directors in my drop down. All right!
Now when I save the movie :
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
movie.Director = db.Directors.Find(movie.Director.ID);
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
The argument movie that the Edit method receives comes with a Director property (as I specified in the model), when I browse into it I see the two properties form director:
"ID": which comes with the proper value that the user selected form the drop down and "Name": set to null.
As you can see in the code I pick the whole director object form the database matching the drop down value and save it
The problem is that when saving the movie, the foreign key on Movies table (Director_ID) never gets updated.
What am I doing wrong? Is there any better approach for doing this?
Make Id of the Director part of your model, like
public class Movie
{
public int ID { get; set; }
[Required]
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
...
public int DirectorId { get; set; }
public virtual Director Director { get; set; }
}
Then in your controller:
//GET
public ActionResult Edit(int id = 0)
{
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
ViewBag.DirectorId = new SelectList(db.Directors, "DirectorId", "Name", movie.DirectorId);
...
}
And in your view:
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
...
<div class="editor-label">
#Html.LabelFor(model => model.DirectorId, "Director")
</div>
<div class="editor-field">
#Html.DropDownList("DirectorId", String.Empty)
#Html.ValidationMessageFor(model => model.DirectorId)
</div>
...

Filling dropdownlist from DB

I'm trying to move over to MVC from webforms, and to be honest, it's not going very well.
I need a DropDownList populated with values from my database, and I'm having a hard time understanding how to do this.
I'm thinking, that I need to create a model,using entity framework, and refer my DropDownList to that? But how do I point it to that model?
Also, if I make a model, from a database table, how do I adjust the select command, so I only get certain values, and not the entire table?
I know this is kind of a broad question, and I have no examples listed, but I've had a really hard time, finding information I could understand, with regards to my issue.
I would start from this this should get you the project created if you have not done so so you can have the model ready
http://msdn.microsoft.com/en-us/data/gg685489
in order to create a dropdownlist here is an example
ViewBag.dropdownlist = new SelectList(db.tablename, "Valuefiled", "NameGField");
where Valuefiled=name of a column in your database that you want to use for values
"NameGField"=name of a column in your database that you want to use for names
getting drop down list to view
#Html.DropDownList("dropdownlist")
How About this
Your ViewModel
public class CategoryViewModel
{
public Category Category { get; set; }
public IEnumerable<SelectListItem> CategoryTitles { get; set; }
}
Your Controller
public ActionResult Create()
{
var categoryviewmodel = new CategoryViewModel();
categoryviewmodel.Category = new Category();
var list = categoryRepository.AllCategoryTitles().ToList().Select(t => new SelectListItem
{
Text = t.CategoryName,
Value = t.CategoryID.ToString()
})
.ToList();
list.Insert(0, new SelectListItem { Value = "0", Text = "Please Selext" });
categoryviewmodel.CategoryTitles = list;
return View(categoryviewmodel);
}
Your Repository
public IQueryable<Category> AllCategoryTitles()
{
var query = context.Categories.Where(m => m.ParentCategoryID == null && m.IsActive==true);
return query;
}
Your View
#Html.DropDownListFor(model => model.CategoryParentID, Model.CategoryTitles)
You can use a viewModel. Here is an example solution with some assumptions. Refer to the dropdownlist (here in the dropdown I am listing departments of type "InBound"
Employee Model
public class EmployeeModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int DeptId { get; set; }
}
Department Model
public class DepartmentModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
}
ViewModel (to be passed into the view)
public class EmployeeViewModel
{
public EmployeeModel Employee { get; set; }
public IEnumerable<DepartmentModel> Departments { get; set; }
}
Controller
public ActionResult Index()
{
EmployeeViewModel vm = new EmployeeViewModel();
//This is hardcoded. However you will write your own method to pull the department details with filtering
List<DepartmentModel> departments = new List<DepartmentModel>() { new DepartmentModel { Id = 1, Name = "Accounts", Type = "InBound" }, new DepartmentModel { Id = 2, Name = "Finance", Type = "OutBound" }, new DepartmentModel { Id = 3, Name = "HR", Type = "InBound" } };
vm.Departments = departments.Where(d => d.Type == "InBound");
return View(vm);
}
View
#model Test1.ViewModels.EmployeeViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm())
{
#Html.HiddenFor(model => model.Employee.Id);
<table>
<tr>
<td>#Html.LabelFor(model => model.Employee.FirstName)</td>
<td>#Html.EditorFor(model => model.Employee.FirstName)</td>
</tr>
<tr>
<td>#Html.LabelFor(model => model.Employee.LastName)</td>
<td>#Html.EditorFor(model => model.Employee.LastName)</td>
</tr>
<tr>
<td>#Html.Label("Department")</td>
<td>#Html.DropDownListFor(model => model.Employee.DeptId, new SelectList(Model.Departments, "Id", "Name"))</td>
</tr>
</table>
}