I've got a simple form that creates Categories with a name and a List of Cars assigned to them.
Everything works fine minus the fact that for each entry in the DB I get a second one before that is null everywhere except for the parent BrandId.
[HttpPost]
public ActionResult AddNewCategory(AddNewCategoryPostViewModel categoryInfo)
{
var brand = _repository.GetBrandById(categoryInfo.BrandId);
if (categoryInfo.Name == null || categoryInfo.Cars == null)
{
if (categoryInfo.Name == null)
{
ModelState.AddModelError("Name", "The name cannot be empty.");
}
if (categoryInfo.Cars == null)
{
ModelState.AddModelError("Cars", "At least one car must be selected.");
}
var cars = _insplib.GetDevCategorysForProject((int)brand.Id);
ViewBag.Cars = cars;
ViewBag.Selectedcars = categoryInfo.Cars;
return View(new Category()
{
Brand = brand
});
}
var category = new Category()
{
DateEntered = DateTime.Now,
IsArchived = false,
Name = categoryInfo.Name,
BrandId = categoryInfo.BrandId
};
_repository.AddOrUpdateCategory(category);
// more code here added to add the cars, but not relevant to this issue.
return RedirectToRoute("Category", new { brand = category.Brand.ShortName, categoryId = category.Id });
}
My repository method is:
public Category AddOrUpdateCategory(Category category)
{
if (category.Id == 0)
_context.AddToCategorys(category);
_context.SaveChanges();
return category;
}
As you can see it's a pretty straight forward POST, yet every time I create a Category, I get two entries:
ID Name DateEntered IsArchived
5 NULL NULL NULL 4
6 NewCategory 10/6/2011 False 4
My trick has been to simply go through the table and remove any Category that has null values in Name. But that's obviously not resolving the actual issue.
As per the comments, It is actually your GET that will be creating the blank record when it passes a new Category() to the view.
Some of the ORM's detect new'd up objects and add them to your context for you. Which helpfully creates confusing issues like this one.
Related
I have the following code
here is how I add a list of values to session
public ActionResult Add(Product product)
{
if (Session["AddToCart"] == null)
{
Session["AddToCart"] = new List<Product>();
}
var list = (List<Product>)Session["AddToCart"];
list.Add(product);
}
but how to remove a single record when a session contains multiple records. I am trying to pass an Id but it is not removing the record from the session. Here is how I perform the next step.
Public ActionResult Remove(Product product)
{
Product prod=db.Products.Single(x=>x.Id==product.Id);
var list=(List<Product>)Session["AddToCart"];
//Is this the correct approach
list.Remove(prod);
}
The above code doesn't works. Am I correct or is there anything missing plz correct the above code. Thanks.
Try this,
var list=(List<Product>)Session["AddToCart"];
list.RemoveAll(p => p.Id == product.Id);
Your choice of finding the product with the code db.Products.Single(x=>x.Id==product.Id); may not be the same object with the one in the session.
Edit:
Or you can implement IEquatable<Product> interface. In this case your code would work too.
public class Product : IEquatable<Product>
{
public int Id;
public bool Equals(Product prod)
{
return prod.Id == Id;
}
// Rest of the class
}
I am using the PCL version of sqlite.net from here (https://github.com/oysteinkrog/SQLite.Net-PCL).
Here is my simple class.
public class LogEntry
{
[PrimaryKey, AutoIncrement]
public int Key { get; set;}
public DateTime Date { get; set; }
}
When a new instance of LogEntry is created, the Key is automatically set to 0. I set the Date to something and then call InsertOrReplace. The record does get saved in my database. The Key field gets the autoincrement value which happens to be 0 since it is the first record.
I then create a new instance of LogEntry (Key is automatically initialized to 0) and set the date to something else. I then call InsertOrReplace. Since there is an existing record with a Key of 0 that record gets updated.
What is the proper way to deal with this? I considered initializing the Key to -1, but that didn't seem to work either.
Does anyone have an example of this working?
If you change the Key to a nullable type (int?) it should work. then SQLite sees null coming in and generates new id when needed.
public class LogEntry
{
[PrimaryKey, AutoIncrement]
public int? Key { get; set;}
public DateTime Date { get; set; }
}
I experienced the same issue as you are describing. Try
var rowsAffected = Connection.Update(object);
if(rowsAffected == 0) {
// The item does not exists in the database so lets insert it
rowsAffected = Connection.Insert(object);
}
var success = rowsAffected > 0;
return success;
I just tried above and it works as expected
The way this works is the source of much confusion but whereas Insert treats zeroed primary keys as a special case when AutoIncrement is set, InsertOrReplace does not.
So with:
[PrimaryKey, AutoIncrement]
public int id { get; set; }
if you InsertOrReplace a series of zero id records into a new table, the first will be stored at id: 0 and each subsequent one will save over it. Whereas if you just Insert each one then because of the AutoIncrement the first will save at id: 1 and the next at id: 2 etc. as you might expect.
If you change the key type to a nullable int, then records with null ids will be treated as inserts by InsertOrReplace, and you don't actually need the AutoIncrement attribute at all in this case, they will still save in sequence starting at 1.
[PrimaryKey]
public int? id { get; set; }
If you can't use that for some reason you can do your own check for zero ids and for those call Insert instead, e.g.
Func<Foo, int> myInsertOrReplace = x =>
{
return x.id == 0 ? _db.Insert(x) : _db.InsertOrReplace(x);
};
but in this case you must use the AutoIncrement attribute, otherwise first zero insert will be saved at 0 and the second will throw a constraint exception when it attempts insert another such.
To get the result you want, you need to make the id property of your class nullable. see here
link
My solution for this is kind of similar to Joacar's, but instead of doing an update, I select the item, if it's null, I create a new item, otherwise update that items values, and then call InserOrReplace.
var existingKey = await this.GetItem(key);
Item item;
if (existingKey.Value != null)
{
profile = new Item
{
Id = existingKey.Id,
Key = existingKey.Key,
Value = newValue,
};
this.InsertOrReplaceAsync(item);
}
else
{
item = new Item
{
Key = key,
Value = value,
};
this.InsertAsync(item);
}
It might not be optimal, but it worked for me.
No need for InsertOrReplace.
Just await InsertAsync.
Guaranteed to work...
if (object.ID != 0)
{
// Update an existing object.
var T = DatabaseAsyncConnection.UpdateAsync(object);
T.Wait();
return T;
}
else
{
// Save a new object.
var T = DatabaseAsyncConnection.InsertAsync(object);
T.Wait();
return T;
}
I am making a web application on MVC3, and I am using linq to communicate with the database.
I made a checkboxlist, where a user can select some options according to their choice and it gets saved in the databse table. The problem is in the Edit part.
The whole scenario is something like this:
The user can register as a restaurant owner or a Motel owner, I have assigned different Business_Type_Id as 1 and 2 for differentiating these two, I have assigned '2' for the Restaurant Business Type, and mapped the cuisines with the perticular business type in the same "Cuisines" table, by adding the "BusinessType" column into the table. the user will be assigned a Business_Id for their Business. I am providing a checkboxlist which generates its options from the database table "Cuisines" where I have given the cuisine list. From the front end the user can choose multiple cuisines according to their chioce what ever they provide in their restaurant. The choices may vary from one restaurant owner to the other, so I am storing the selected values for each and every Restaurant owner in a "BusinessCuisinesMapping" table, where I map the perticular BusinessId with the selected CuisineId by that perticular user.
Now to populate that cuisine list for edit or update I wrote a linq join, but I need to compare it with the Business_Id which is passed to the [HttpGet] ActionResult Edit. And this is point where I got stuck.
This is my linq join code which I am using in the controller:
[HttpGet]
public ActionResult Edit(int id)
{
using (var chkl = new BusinessEntities())
{
var data = (from CuisinesData in chkl.Cuisines
join BusinessCuisineMappingData in chkl.BusinessCuisineMapping
on new { CuisinesData.Id, id } equals new { BusinessCuisineMappingData.CuisinesId, BusinessCuisineMappingData.BusinessId }
where CuisinesData.BusinessTypeId == 2
select new CusinesDTO
{
Id = CuisinesData.Id,
Name = CuisinesData.Name,
IsSelected = BusinessCuisineMappingData.CuisinesId == null ? false : true
}).Distinct().ToList();
ViewBag.CuisineList = data;
}
return View();
}
This is my DTO class:
public class CusinesDTO
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
I want to comape the "id" with the "BusinessCuisineMappingData.BusinessId" field in my LINQ join, which I am getting through the [HttpGet] Actionresult Edit(int id). It prompts me an error while I try to implement it.
You cannot use a local variable in the join. However, you can use it in a Where clause. So if you do
join ...
on CuisinesData.Id equals BusinessCuisineMappingData.CuisinesId
...
where BusinessCuisineMappingData.BusinessId == id
you'll have the same effect.
"The type of the expressions in join clause is incorrect. Type inference failed in the call to 'Join'"
This suggests the types on either side of your JOIN are not equal. You are getting a compiler error; should be relatively easy to check the types on either side of your JOIN clause.
edit: rereading what you have put, I think something like the below is what you want to do:
[HttpGet]
public ActionResult Edit(int id)
{
using (var chkl = new BusinessEntities())
{
var data = (from CuisinesData in chkl.Cuisines
join BusinessCuisineMappingData in chkl.BusinessCuisineMapping
on CuisinesData.Id equals BusinessCuisineMappingData.CuisinesId
where CuisinesData.BusinessTypeId == id
select new CusinesDTO
{
Id = CuisinesData.Id,
Name = CuisinesData.Name,
IsSelected = BusinessCuisineMappingData.CuisinesId == null ? false : true
}).Distinct().ToList();
ViewBag.CuisineList = data;
}
return View();
}
I was hoping there was an easier way to do this in my MVC 3 project. In my database, I have a Customer table that is mapped in my application via LINQ2SQL. There is also a partial customer class where I perform updates, look-up etc - which where I have an update method like this:
public static void Update(Customer customer)
{
if (customer == null)
return;
using(var db = new DataBaseContext)
{
var newCustomer = db.Customers.Where(c => c.customer_id = customer.customer_id).SingleOrDefault();
if(newCustomer == null)
return;
newCustomer.first_nm = customer.first_nm;
// ...
// ... Lot's of fields to update
// ...
newCustomer.phone_num = customer.phone_nm;
db.SubmitChanges();
}
}
What I was hoping to find was a less-cumbersome method to update the fields in newCustomer with the corresponding fields in customer that are different.
Any suggestions? Thanks.
I think you can implement IEqualityComparer:
public class Customer
{
public string first_nm { get; set; }
public int phone_num { get; set; }
}
class CustomerComparer : IEqualityComparer<Customer>
{
public bool Equals(Customer x, Customer y)
{
//Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
//Check whether the customer' properties are equal.
return x.first_nm == y.first_nm && x.phone_num == y.phone_num ;
}
}
and do it as follows:
if (newCustomer != customer)
{
myDbContext.Customers.Attach(customer,true); // true means modified.
}
Or implement ICloneable and set newCustomer to customer.Clone(). then there's no need to attach customer since newCustomer is already attached.
in EF(4.1), I think You just have to attach the entity as modified:
myDbContext.Customers.AttachAsModified(customer, this.ChangeSet.GetOriginal(customer), myContext);
UPDATE:
Well, it seems like L2S needs original values of the entity. In reply to your comment, you have a couple choices: Using a timestamp column, returning a subset of entities, or having the original entity in your hand. In your scenario, you have the original entity already:
// This is your original entity
var newCustomer = db.Customers.Where(c => c.customer_id = customer.customer_id).SingleOrDefault();
So you will most probably can do:
if (customer != newCustomer)
{
myDbContext.Customers.Attach(customer, newCustomer);
}
Note: I'd rename newCustomer to originalCustomer if I were you since it's more related to the entity's state.
The problem with this approach is that you have an extra trip to database to get your original customer (newCustomer in your code). Take a look at here, here and definitely here to see how you can use TimeStamp columns to prevent the extra database trip.
We found strange behaviour in DropDownListFor (ASP.NET MVC3 release). It selects ViewBag property value instead of Model property value in dropdown.
Model:
public class Country {
public string Name { get; set; }
}
public class User {
public Country Country { get; set; }
}
Controller Index action:
ViewBag.CountryList = new List<Country> { /* Dropdown collection */
new Country() { Name = "Danmark" },
new Country() { Name = "Russia" } };
var user = new User();
user.Country = new Country(){Name = "Russia"}; /* User value */
ViewBag.Country = new Country() { Name = "Danmark" }; /* It affects user */
return View(user);
View:
#Html.EditorFor(user => user.Country.Name)
#Html.DropDownListFor(user => user.Country.Name,
new SelectList(ViewBag.CountryList, "Name", "Name", Model.Country), "...")
It will show text box with "Russia" value and dropdown with "Danmark" value selected instead of "Russia".
I didn't find any documentation about this behaviour. Is this behaviour normal? And why is it normal? Because it is very hard to control ViewBag and Model properties names.
This sample MVC3 project sources
I'm not so sure why this decision was made, but it was happened because MVC framework tried to use the ViewData-supplied value before using the parameter-supplied value. That's why ViewBag.Country override parameter-supplied value Model.Country.
That was how it was written in MVC framework in the private method SelectInternal.
object defaultValue = (allowMultiple) ? htmlHelper.GetModelStateValue(fullName, typeof(string[])) : htmlHelper.GetModelStateValue(fullName, typeof(string));
// If we haven't already used ViewData to get the entire list of items then we need to
// use the ViewData-supplied value before using the parameter-supplied value.
if (!usedViewData) {
if (defaultValue == null) {
defaultValue = htmlHelper.ViewData.Eval(fullName);
}
}
if (defaultValue != null) {
IEnumerable defaultValues = (allowMultiple) ? defaultValue as IEnumerable : new[] { defaultValue };
IEnumerable<string> values = from object value in defaultValues select Convert.ToString(value, CultureInfo.CurrentCulture);
HashSet<string> selectedValues = new HashSet<string>(values, StringComparer.OrdinalIgnoreCase);
List<SelectListItem> newSelectList = new List<SelectListItem>();
foreach (SelectListItem item in selectList) {
item.Selected = (item.Value != null) ? selectedValues.Contains(item.Value) : selectedValues.Contains(item.Text);
newSelectList.Add(item);
}
selectList = newSelectList;
}
This code defaultValue = htmlHelper.ViewData.Eval(fullName); tried to get the value from ViewData and if it can get the value, it will override the supplied parameter selectList with new list.
Hope it can help. Thanks.
side-node: ViewBag is just a dynamic wrapper class of ViewData.
The following line from your action method is what is confusing the code:
ViewBag.Country = new Country() { Name = "Danmark" }; /* It affects user */
That's because the html helpers look into a few different places to pick up values for the generated controls. In this case ViewData["Country"] is clashing with ModelState["Country"] Rename that property to something else and everything should work.