I have a problem with the validation of Symfony. I have a form which is of the type User and the user maps some other stuff (like Addresses, Phones etc.)
Now I want to force the creator of the user that he makes one of the Addresses/Phones the primary one (the entity has a field for that).
How can I solve this? So only one of the OneToMany Entitys (one of the Addresses) needs to be a primary one. And assure that it will be always at least one.
One way is to add a field to the User entity pointing to a primary address in a one-to-one manner and make it required.
Another way is to create a custom validator that will loop through the user addresses and validate that at least one of them is marked as primary.
Or you could just use the True constraint:
/**
* #True
*/
public function isThereOnePrimaryAddress()
{
$primes = 0;
foreach ($this->getAddresses() as $address) {
if ($address->isPrimary()) {
$primes++;
}
}
if (1 === $primes) {
return true;
}
return false;
}
Related
I have this API which deletes and adds different users into the the Database. The way this API is written is something like this:
/* Assuming 'users' is an array of users to delete (if users[x].toDelete == true),
or to add otherwise */
using (var db = new myContext())
{
using (var dbTransaction = db.Database.BeginTransaction())
{
try
{
/* Performing deletion of Users in DB */
SaveChanges();
/* Performing add of Users in DB */
foreach(user in users)
if (!user.toDelete) {
db.users.add(..);
db.SaveChanges();
}
dbTransaction.Commit();
}
catch (ValidationException ex)
{
dbTransaction.Rollback();
}
}
}
The reason why I use a transaction is that I have some validations that should be run on the new data.. For example, I cannot add two user with the same email!
All the validation Server-Side is done with Data-Annotations and the MetaData classes, therefore I created the Attribute Uniqueness which I associated to the property email of the class UserMetaData.
So, the problem is that, inside the Uniqueness Attribute I need to check again the database to search for other users with the same email:
public class IsUnique : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
...
using (var db = new myContext()) {
/* Here I perform a select on the DB looking for records
with the same email, all using Reflection. */
if (recordFound > 0)
return new ValidationResult("email already exists");
return ValidationResult.Success;
}
}
}
So, as you can see, inside the validation I use new myContext() and that's where the problem is: let's pretend I have an empty database and I add two user with the same email in the same query.
As you can see, I call the db.SaveChanges() after every user has been added, therefore I thought that, when the second user to be added would have been validated, my validation would have known that the first user added and the second one have the same email, therefore an Validation Exception would have been throw and the dbTransaction.Rollback() called.
The problem is that inside the validator i call new myContext() and inside that context the changes that I thought would have been there thanks to the db.SaveChanges() there arent't, and that's I think is because all the changes are inside a Transaction.
Sooo.. The solutions I thought so far are two:
Nested Transaction: in the API I have an outer transaction which save the state of the DB at the beginning, then some inner transaction are run every time a db.SaveChanges() is called. Doing this, the validation does see the changes, but the problem is that, if in the catch i called outerTransaction.rollback(), the changes made by the inner transactions are not rollbacked;
Some way to retrieve the context I have used in the API even in the validation.. But in there I only have these two arguments, so I don't know if it's even possible;
I have two entities Inspection and User
An Inspection has an Approver property which is a User hooked up like so public virtual User Approver { get; set; }
Sometimes Approver has to be set to null
I am doing it as follows
Inspection rtrn = Context.Inspections.Where(x => x.Id == inspId).SingleOrDefault();
rtrn.Approver = null;
rtrn.ApprovalDate = new DateTime();
base.Update(rtrn); //EF repo
The problem is that this does not set Approver_Id to null in the database. In fact if I put a break point at the rtrn.ApprovalDate = new DateTime(); I can see that rtrn.Approver is still of type User. It looks as if it's ignoring the rtrn.Approver = null; line all together.
At the higher level I just want to break Inspection -- User association without deleting either entity.
Thank you!
Your virtual property is just that, virtual. It is filled with the record that matches its corresponding foreign key, which is typically ApproverId (unless you specified a different ForeignKey with the ForeignKey attribute).
If you want to get rid of the Approver, then you just need to set the ApproverId to null.
I'm using Sails.js 0.10.x. I'm referring to the functionality documented as "Create new record while associating w/ another new record (REST)" in combination with a multi-tenant system. All models have got a related_account as part of the primary key. And I need to find a bullet proof way to have it always filled with the current users related account.
In a parent-child relationship it is easy to fill the related_account on the parent by overriding the blueprint and simply setting the related_account property before the records are created. But as the javascript object is passed to Model.create(parent_and_children) it would mean that I would have to loop over all the children and set the related_account manually before creation. This work is tedious and error prone because there are a lot of situations where I need that. Furthermore this is a possible cause of critical defects as we work in a team and someone could forget to add it. In some cases the standard blueprints might be used, in other cases actions could be implemented manually.
There must be a better way than always setting it manually. Any idea is appreciated.
By the way: The value for the related_account is available in the request variable "user".
Maybe this can help. I am doing the same where I need the companyId of the logged in users on every record (found in session.user.company.id). So this was my solution. It is a policy that checks if the model has any associations and then loops through those associations adding the companyId in the req.body object.
module.exports = function(req, res, next) {
req.options.where = _.assign({companyId : req.session.user.company.id}, req.options.where);
var attachCompanyIdToBody = function(){
if(_.isArray(req.body)){
} else if (_.isObject(req.body)){
req.body.companyId = req.session.user.company.id;
attachCompanyIdToAssociations();
}
};
var attachCompanyIdToAssociations = function(){
// Get assocations with current model
var Model = actionUtil.parseModel(req);
if(Model.associations){
_.forEach(Model.associations, function(item,index,object){
// if has company attached, remove company
if(req.body[item.alias] && item.alias.toLowerCase() !== 'company'){
if(_.isArray(req.body[item.alias])){
// ToDo if assocation is a collection then loop through collection adding companyId
} else if(_.isObject(req.body[item.alias])){
// if assocation is object then add companyId to object
req.body[item.alias].companyId = req.session.user.company.id;
}
}
});
}
};
if(req.body){
attachCompanyIdToBody();
}
return next();
};
I currently have an object like this (simplified):
public class Image {
public int Id { get; set; }
public int ExternalId { get; set; }
}
Now let's say I have this method (mostly pseudo-code):
public void GetImage(int externalId) {
var existingImage = db.Images.FirstOrDefault(i => i.ExternalId == externalId);
if (existingImage != null) {
return existingImage;
}
var newImage = new Image() { ExternalId = externalId };
db.Images.Attach(newImage);
db.SaveChanges();
return newImage;
}
Because ExternalId isn't a key, the change tracker won't care if I have "duplicate" images in the tracker.
So now, let's say this method gets called twice, at the same time via AJAX and Web API (my current scenario). It's async, so there are two threads calling this method now.
If the time between calls is short enough (in my case it is), two rows will be added to the database with the same external ID because neither existing check will return a row. I've greatly simplified this example, since in my real one, there's a timing issue as I fetch the "image" from a service.
How can I prevent this? I need the image to be returned regardless if it's new or updated. I've added a Unique Constraint in the database, so I get an exception, but then on the client, the call fails whereas it should use the existing image instead of throwing an exception.
If I understand EF correctly, I could handle this by making ExternalId a primary key and then use concurrency to handle this, right? Is there any way to avoid changing my current model or is this the only option?
If you already have property defining uniqueness of your entity (ExternalId) you should use it as a key instead of creating another dummy key which does not specify a real uniqueness of your entity. If you don't use ExternalId as a key you must put unique constraint on that column in the database and handle exception in your code to load existing Image from the database.
Okay, I'm really struggling with how to update a list of foreign keys in MVC2/EF4.
I have a one to many relationship between a Template object which can have many or no TemplateScenario objects.
Essentially, I have an edit method in a controller that is trying to do this:
// POST: /Modes/Edit/1
[HttpPost]
public ActionResult Edit(int id, FormCollection formValues)
{
Template template = _templateRepository.GetTemplate(id);
TemplateCreateViewModel viewModel = new TemplateCreateViewModel();
viewModel.Template = template;
viewModel.TemplateScenarioList = template.TemplateScenarios.ToList();
//Update the model
UpdateModel(viewModel);
UpdateModel(viewModel.Template.TemplateScenarios, "TemplateScenarioList", new[] { "ScenarioID", "TemplateID" });
_templateRepository.Save();
return RedirectToAction("Edit", new { id = template.TemplateID });
}
This code successfully updates the 'template' object. It also adds the 'templatescenario' child objects BUT only if it is the first time I have added 'templatescenarios' to this particular template. If any templatescenario objects already exist for a given template, and I try to update them based on the new list, I get this error:
"The operation failed: The relationship could not be changed because one or more
of the foreign-key properties is non-nullable. When a change is made to a
relationship, the related foreign-key property is set to a null value. If the
foreign-key does not support null values, a new relationship must be defined,
the foreign-key property must be assigned another non-null value, or the
unrelated object must be deleted."
The _templateRepository.Save(); is just calling the entities.SaveChanges() EF4 method.
I can solve this in a dirty way by passing down a list of templatescenario ids to my repository class in a custom 'update' method that looks like this:
public void Update(Template template, IList<int> templateScenarios)
{
//Delete Old Entries
foreach (TemplateScenario ts in entities.TemplateScenarios)
{
if (ts.TemplateID == template.TemplateID)
{
if (templateScenarios == null)
entities.TemplateScenarios.DeleteObject(ts);
else if (!templateScenarios.Where(tsl => tsl == ts.ScenarioID).Any())
entities.TemplateScenarios.DeleteObject(ts);
}
}
//Don't need to add anything if they are null.
if (templateScenarios == null)
return;
//Add New Entries
foreach (int ts in templateScenarios)
{
if (!entities.TemplateScenarios.Where(tsc => tsc.ScenarioID == ts && tsc.TemplateID == template.TemplateID).Any())
{
TemplateScenario tempScenToAdd = new TemplateScenario();
tempScenToAdd.ScenarioID = ts;
tempScenToAdd.TemplateID = template.TemplateID;
entities.TemplateScenarios.AddObject(tempScenToAdd);
}
}
}
But that just feels dirty and I think I'm so close with the first, more automatic method. I've scoured the internet and found some similar posts on stackoverflow but am finding it difficult to reach that 'aha' moment.
Thanks,
Tom.
Incidently, I sorted out my problem.
The problem was my joining table was incorrectly using it's own primary key instead of using a composite key based on two foreign keys. This is obviously wrong /bad practice and EF4 and UpdateModel() don't play nice.
I had inherited the DB design from an ex-collegue and thus had taken the db design as correct without thinking too much about it. Very stupid of me, I know.