I have simple method to update user rating:
public void PostScore(int userId, GlobalSettings gs, string name)
{
User user = _usrRepo.GetById(userId);
if (name == "up")
{
user.Rating = user.Rating + gs.ScoreForLike;
}
else if (name == "down")
{
user.Rating = user.Rating - Math.Abs(gs.ScoreForDislike);
}
_ctx.SaveChanges();
}
The problem is, that user rating do not update.. I mean the changes are not saved to database.
Is there way to debug what's going and and why EF4.1 do not save data to database ?
It looks like your are getting your User object through a repository (_usrRepo) that is using a different context than the one you are are calling SaveChanges() on (_ctx) - I bet this is your problem.
Related
I'm discovering ASP.NET Core MVC and I'm on my first project. Creating a cool web shop.
I'm currently wondering how to implement faulty information checking for example in the controller
Let's say there a product page, whenever users clicks on a product they will hit the function below.
As you can see the function accepts an int parameter named id, it will search in the database for the id that fits the productId, but I'm wondering how do I add error checking here? Like for example if the id does not exist in database return to page XX?
Also feel free to give suggestions to the function if you don't like it.
I've already tried to do a simple if and else statement
if(productvm == null)
{
then
return RedirectToPage("Index")
}
else
return View("ProductPage", productVm);
but it didn't seem to hit the if statement
[Route("ProductPage/{id}")]
public IActionResult ProductPage(int id)
{
Product product = _uow.Products.SelectProduct(id);
var stockViewModels = new List<StockViewModel>();
foreach (Stock stock in product.Stock)
{
stockViewModels.Add(new StockViewModel()
{
Id = stock.Id,
Description = stock.Description,
IsAvailable = stock.IsAvailable,
Quantity = stock.Quantity,
});
}
ProductViewModel productVm = new ProductViewModel
{
Name = product.Name,
Id = product.Id,
Description = product.Description,
Price = product.Price,
Stocks = stockViewModels,
};
if (productVm == null)
{
return RedirectToPage("Productslist");
}
else
{
return View("ProductPage", productVm);
}
}
I basically want an error handling the controller if the id is not found in the database then execute XX
The way how I test the function is to change the ID when browsing the page with an ID that does not exist in the database, then I get this error:
https://i.imgur.com/1amWx43.png
and I want to handle it
I think your problem is that you have new the productVm object before the if, so it will never be null, for your case, you should get check the product object and not the productVm, for example:
Product product = _uow.Products.SelectProduct(id);
if (product == null)
{
return RedirectToPage("Productslist");
}
else
{
return View("ProductPage", productVm);
}
Is the essence of Project, the creation of which is necessary to check whether there is already an entity with the same name. When editing needs such as checking, but keep in mind that the old and the new name of the entity can be matched.
You also need to display an error message. For this I use interface IValidatableObject, but do not know how to tell the Validate method the object is currently being edited or created
DbContext.ValidateEntity takes the IDictionary<Object, Object> items as the second parameter. You can pass any data there and the data you pass will be passed to IValidatableObject.Validate in the ValidationContext.Items
Assuming you refer to check EF cant do for you.
This is actually difficult to check. You are checking an entity after it has been added to the context. It should not check itself and needs to consider other items in context that are not yet saved. As well as the DB. There are several 3 combinations plus an self recognition. Record a an entity record in LOCAL when ID is blank/new ie multiple new inserts needs careful coding. (Consider using temp IDs)
the not yet saved entries should be in context
Context.Set<TPoco>().Local
and get data from DB and keep in a temp list. BUT dont put in context.
Or use a SECOND context.
var matchingSet = Context.Set<TPoco>().AsNoTracking() // not into context...
.Where(t=>t.field == somevalue).ToList();
So what about logical and actual duplicates on the DB. Logical duplicates are duplicates on a field with no unique index that from a business perspective should be unique.
If you want to check those...
You need to read the DB.... BUT if these records are currently being changed, you CAN NOT just put them into the Context. You would overwrite them.
But what if the values the logical key values have changed?
Something caused a logical dup on a record on the DB may no longer be a dup once saved or vice verse. Is that still a dup or not ?
So you need to decide how you match LOCAL versus loaded records.
Ie check LOCAL and matching DB records and decidr what to do if a record is in both, only local or only db.
LOCAL ONLY and DB Only is easy.
But in both... That is your business process decision.
Problem is solved using method ModelState.AddModelError (string, string) in actions Edit and Create.
[HttpPost]
[HandleError(View="AjaxError")]
public ActionResult Edit(ProjectsViewData data)
{
if (ModelState.IsValid)
{
if (!ContainsProject(data.CurrentObject.Name))
{
db.Projects.Attach(data.CurrentObject);
db.ObjectStateManager.ChangeObjectState(data.CurrentObject, EntityState.Modified);
db.SaveChanges();
return Projects(data);
}
else
{
int projectId = (from p in db.Projects
where p.Name == data.CurrentObject.Name
select p.ProjectID).FirstOrDefault();
if (projectId == data.CurrentObject.ProjectID)
{
db.Projects.Attach(data.CurrentObject);
db.ObjectStateManager.ChangeObjectState(data.CurrentObject, EntityState.Modified);
db.SaveChanges();
return Projects(data);
}
else
{
ModelState.AddModelError("Name", Localizer.ProjectAlreadyExists);
}
}
}
data.ObjectToEdit = data.CurrentObject;
return Projects(data);
}
[HttpPost]
[HandleError(View = "AjaxError")]
public ActionResult Create(ProjectsViewData data)
{
if (ModelState.IsValid)
{
if (!ContainsProject(data.CurrentObject.Name))
{
db.Projects.AddObject(data.CurrentObject);
db.SaveChanges();
return Projects(data);
}
else
{
ModelState.AddModelError("Name", Localizer.ProjectAlreadyExists);
}
}
data.ObjectToAdd = data.CurrentObject;
return Projects(data);
}
Helper method:
private bool ContainsProject(string projectName)
{
if (projectName != null)
{
projectName = Regex.Replace(projectName.Trim(), "\\s+", " ");
List<string> projects = new List<string>();
var projectNames = (from p in db.Projects
select p.Name.Trim()).ToList();
foreach (string p in projectNames)
{
projects.Add(Regex.Replace(p, "\\s+", " "));
}
if (projects.Contains(projectName))
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
I try to add a new value to my database. UserPassword and RePassword must have the same value and a user with UserName must not already exist in the database.
public User NewUser(int HotelID, string UserName, string UserPassword, string RePassword, string FullName, string Email, bool Active, bool MasterUser)
{
User user = new User();
user.HotelID = HotelID;
user.UserName = UserName;
user.UserPassword = UserPassword;
user.FullName = FullName;
user.Email = Email;
user.IsActiveq = Active;
user.IsMaster = MasterUser;
var cekUser = (from c in _UserRepository.All()
where c.HotelID == HotelID
select c.UserName).ToList();
if (UserPassword == RePassword)
{
foreach (string cek in cekUser)
{
var x = cek;
if (UserName != x)
{
_UserRepository.Add(user);
}
}
}
_UserRepository.CommitChanges();
return user;
}
Every time I run my code a new line is added to the database, although a user with the supplied user name already exists in the database.
Why does this happen? Which part of my code is wrong?
I think your code should be something like this:
if (UserPassword == RePassword)
{
// Also I thinks you should finish whether user existed logic in database
// but for now, let's follow your original logic
var existedUsers = (from c in _UserRepository.All()
where c.HotelID == HotelID
select c.UserName).ToList();
if (!existedUsers.Any(u => u == UserName))
{
_UserRepository.Add(user);
_UserRepository.CommitChanges();
}
}
You have your logic wrong. If there is more than one user in a given hotel, your code will be adding more users for all users with names different from UserName.
bool found = false;
foreach(string cek in cekUser)
{
if ( UserName == cek)
{
found = true;
break;
}
}
if (!found)
_UserRepository.Add(user);
Just offering an alternate idea.
If you have access to the database, the best approach will be to make the Username field UNIQUE. That way, even if you get your code wrong, a duplicate insert will fail. Then you capture that fail gracefully in your Repository, and Bob's your uncle.
Let's say I have this code:
class Score
{
public Update(int score)
{
update score but do not call (context.SaveChanges())
}
}
class Foo
{
public DoSomething(int update)
{
Score score = new Score();
score.Update(2);
SomeObj obj = (select object);
obj.Soo = 3;
context.SaveChanges();
}
}
Basically to make it work, I need to explicity provide SaveChanges in method Update. But when I have 4 such methods in row, and 34243 users want to update data, I don't think saving for each one in 4 trips would be a good idea.
Is there way in EF4.1 to delay database update the last moment, in provided example, Or I'm forced to explicity save for each method ?
EDIT:
For clarification. I tried to do not call SaveChanges in external method, and only one time where the changes mu be saved.
I will give an real example:
public class ScoreService : IScoreService
{
private JamiContext _ctx;
private IRepository<User> _usrRepo;
public ScoreService(IRepository<User> usrRepo)
{
_ctx = new JamiContext();
_usrRepo = usrRepo;
}
public void PostScore(int userId, GlobalSettings gs, string name)
{
User user = _ctx.UserSet.Where(x => x.Id == userId).FirstOrDefault();
if (name == "up")
{
user.Rating = user.Rating + gs.ScoreForLike;
}
else if (name == "down")
{
user.Rating = user.Rating - Math.Abs(gs.ScoreForDislike);
}
}
}
And Now:
public PostRating LikeDislike(User user, int postId, int userId, GlobalSettings set, string name)
{
PostRating model = new PostRating();
var post = (from p in _ctx.PostSet
where p.Id == postId
select p).FirstOrDefault();
if (name == "up")
{
post.Like = post.Like + 1;
model.Rating = post.Like - post.Dislike;
}
else if (name == "down")
{
post.Dislike = post.Dislike + 1;
model.Rating = post.Like - post.Dislike;
}
PostVote pv = new PostVote();
pv.PostId = post.Id;
pv.UserId = user.Id;
_ctx.PostVoteSet.Add(pv);
_scoreSrv.PostScore(userId, set, name);
_ctx.SaveChanges();
return model;
}
I this case user rating do not update, Until I call SaveChanges in PostScore
In your example it looks like PostScore and LikeDislike use different context instances. That is the source of your problem and there is no way to avoid calling multiple SaveChanges in that case. The whole operation is single unit of work and because of that it should use single context instance. Using multiple context instances in this case is wrong design.
Anyway even if you call single SaveChanges you will still have separate roundtrip to the database for each updated, inserted or deleted entity because EF doesn't support command batching.
The way to delay database update to the last moment is by not calling SaveChanges until the last moment.
You have complete control over this code, and if your code is calling SaveChanges after every update, then that needs changing.
This not really solves my entire problem, but at least I can use single instance of Context:
With Ninject:
Bind<JamiContext>().To<JamiContext>().InRequestScope();
And then constructor:
private JamiContext _ctx;
private IRepository<User> _usrRepo;
public ScoreService(IRepository<User> usrRepo, JamiContext ctx)
{
_ctx = ctx;
_usrRepo = usrRepo;
}
I am using Entity Framework 4.0 POCO with WPF.
I have a form displaying to the user a an object with a many to many relationship like the following:
public class BPartner : BaseEntity
{
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = bpartnerValidatorLight.ValidateName(value);
OnPropertyChanged("Name",true);
}
}
}
public virtual ObservableCollection<BPAddress> BPAddresses { get; set; }
}
public class BPAddress : BaseEntity
{
public string Line1
{
get
{
return line1;
}
set
{
if (line1 != value)
{
line1 = bpAddressValidatorLight.ValidateLine1(value);
OnPropertyChanged("Line1",true);
}
}
}
public virtual City City
{
get { return city; }
set
{
if (city != value)
{
city = value;
OnPropertyChanged("City");
}
}
}
}
The user may add and delete addresses in the BPAddresses collection and change the "Name" of the BPartner. When the user is done modifying the BPArtner object he/she may click "Save" or "Cancel". The problem is that when the user clicks "Cancel" I need to tell the Entity Framework to revert all the changes.
Any approach for handling this is very welcome including reloading.
Here is what I have tried:
1. Discard the objectContext, create a new object context and just query the database to reload everything again. The problem here is that Entity Framework caches things and old objects linger attached to the old context and I get exceptions if the user clicks cancel then edits again and clicks save.
2.
repoBPartner.Refresh(bp);
IQueryable<BPAddress> query = from e in addressRepo.AsQueryable()
where e.BPartnerId == bp.Id
select e;
ObjectQuery<BPAddress> objectQuery = (ObjectQuery<BPAddress>)query;
objectQuery.Include("City");
objectQuery.Include("Country");
ObjectResult<BPAddress> result = (ObjectResult<BPAddress>)objectQuery.Execute(MergeOption.OverwriteChanges);
bp.BPAddresses = new System.Collections.ObjectModel.ObservableCollection<BPAddress>(result.ToList<BPAddress>());
The problem here is that "City" property does not get refreshed.
3. Tried: objectContext.LoadProperty(bp, "BPAddresses", MergeOption.OverwriteChanges);
All the above worked partially. What is the best practice for achieving this?
Any help will be appreciated.
Thanks,
K. Mitev
EF doesn't offer discarding changes. EF also doesn't have second level cache so once you dispose a context with changes and create a new context to load data from database you will get unchanged data.
The problem here is that Entity
Framework caches things and old
objects linger attached to the old
context and I get exceptions if the
user clicks cancel then edits again
and clicks save.
When user clicks cancel you must throw away all objects used in editation together with their context. These objects and context are dead. Once user clicks edit again you must load everything again from database.
Refreshing navigation properties doesn't work very well. For example once you add something to navigation collection it will never be removed by refreshing from database.