If I have one-to-many relation between two entities (ie. Post and Comment) and have my master class defined as:
public class Post {
...
IList<Comment> Comments { get; set; }
}
But my Comment sub-related class doesn't have a property of type Post, because there's never a need to get from comment to post. Comments are always displayed along with the master post instance.
Then I have a stored procedure that returns two result sets: posts and comments that are related to them. I define my MapResultSet as
MapResultSet[] sets = new MapResultSet[2];
sets[0] = new MapResultSet(typeof(Post), posts);
sets[1] = new MapResultSet(typeof(Comment));
sets[0].AddRelation(sets[1], /* what goes here? */, "PostID", "Comments");
But this doesn't work, since Comment doesn't have a reference to its Post hence I don't have anything to define for the second parameter in the upper code. If I provide string.Empty or null I get an exception if invalid method parameter.
How should I define relationship between these two entities without adding a Post property to Comment?
Comment should have "PostId", if it doesn't it is impossible to define the post for the comment (for example in result sets we have 2 posts and 7 comments)
So you should add either Post property or PostId to the Comment class.
Also for simple cases please see the RelationAttribute, the example is here
See Test2(), this helps to avoid manually relations prepare, no need to write:
sets[0].AddRelation(sets[1], "PostID", "PostID", "Comments");
Related
Using ODataController In one to many to many or in many-to-many relationship, how can I GET child of child entities.
For example in the OData 4 Sample Service here:
https://github.com/OData/ODataSamples/blob/master/Scenarios/TripPin/src/webapi/ODataSamples.WebApiService/Controllers/PeopleController.cs
I need to know how to implement something like:
public class PeopleController : ODataController
{
...
[ODataRoute("People({key})/Trips/PlanItems)]
public IQuerable<PlanItems> GetPlanItems([FromODataUri] string key])
}
That means I want to retrieve all PlanItems for a Person.
Unfortunately, all methods implement [ODataRoute("People({key})/Trips({tripId})/PlanItems)] only
The URL convention .../People(key)/Trips/PlanItems is not valid as you can see from the error message if you try "The request URI is not valid. Since the segment 'Trips' refers to a collection, this must be the last segment in the request URI or it must be followed by an function or action that can be bound to it otherwise all intermediate segments must refer to a single resource." So the alternative is using URL .../People(key)/Trips?$expand=PlanItems
I'm beginner in Grails and I have a problem when I try to save a POGO
I have created 1 domain class
class Book {
String title
}
Then, I have generated the controller and view automatically.
Now, I want to be able to create a book with the code by clicking "create" (I know it is possible directly with the code generated but for my example I want to do it by the code). To do this, I have modified the method 'save(Book bookInstance)' in the controller like this
#Transactional
def save(Book bookInstance) {
def book = new Book(title:"New Grails Book").save()
But, when I go to the URL localhost:8080/myApp/book/create and then I click "Create", I have the error
message -> /myApp/WEB-INF/grails-app/views/book/save.jsp
description -> The requested resource is not available.
When I put this code in bootStrap, it is OK, so I don't understand why it is not in the controller
When you have a hasMany property in a domain class, Grails adds a Set property to the domain class with an AST transformation (so it's actually there in the bytecode, and it's visiable to Java) to represent the collection, and when you add a belongsTo a field of that type is added. So it's as if you had this code:
class Author {
Set<Book> books
static hasMany = [books: Book]
String name
}
and
class Book {
Author author
static belongsTo = [author: Author]
String title
}
The AST xform uses the map key as the field name, so you can use any valid field name, but the convention is to do what you did.
Properties are nullable:false by default, so your code doesn't save the Book instance because you didn't set the author property. When doing this explicitly you typically don't create the Book directly, but instead add it to the Author's collection using the dynamic addToBooks method. This sets the author field back-reference and when you save the author, the book is transitively validated and saved. This is all handled for you when you have code like new Book(params).save(), and you can do it directly, e.g.
Author author = ...
def book = new Book(title:"New Grails Book", author: author).save()
If you're using a generated controller and GSPs, there should be an author id in the params map, it'll likely be author.id, so that first line would be
Author author = Author.get(params['author.id'])
but you can add
println params
at the top of the action method to see all of the submitted params.
In general you don't want to look at the return value of the save call, since it will be null if there's a validation error and there's no way to retrieve the errors. So change
def book = new Book(...).save()
to
def book = new Book(...)
book.save()
and now you can call book.hasErrors(), book.getErrors(), book.errors, etc. to see if it was successful and if not, what went wrong.
But that's not the exact problem you're seeing, just one you will when you fix your problem. There's no save.gsp, and Grails also looks for save.jsp and confusingly includes that name in the not-found message. The save method is accessed via a POST request, typically from the form generated by the create action, and it either re-displays create.gsp with the submitted data and error messages when validation fails, or redirects to the view action when the save succeeds. There's no need for a save.gsp when using the generated code.
I have a Note domain object which belongs to a Document object. Only an owner of a Document can add Notes so in the Document class there is a canUserAccess() method. In my service layer I can call canUserAccess() to ensure a user only adds Notes to Documents they own.
This works well for create but I have hit a problem with my Note edit action. On post, the viewmodel is mapped to a Note object, providing it with a DocumentID. Problem is, a malicious user could send in a DocumentID on which they do have permission and thus edit a Note belonging to a Document they don't. In my service layer I cannot reliably use the supplied DocumentID yet I need to get the related DocumentID in order to verify that the user can edit the Note. This is an example:
public void editNote(Note note)
{
note.Document = documentRepository.Find(note.DocumentID);
if(note.Document.canUserAccess())
}
How do I get around this? It seems I need to avoid passing the DocumentID with the edit viewmodel but how do I hydrate the related Document in the service layer? There is probably a really simple solution to this and I am just tying myself up in circles!
You do this with BindAtrribute for the model or for the action method by adding a white list with the properties you want to be bound :
for the model
[Bind(Include="NoteText,NoteTitle")]
public Model{}
for the action method
public ViewResult Edit([Bind(Include="NoteText,NoteTitle"]Note note)){}
or use a black list for the properties you don't want to bind :
[Bind(Exclude="DocumentID")]
public Model{}
I would personally use white list with the model class. You might find this article interesting. The last section for under-posting is your case.
Then you don't have the documentID passed, but in your action you can do this:
public ViewResult Edit(Note note)
{
Note updateNote = nodesRep.Get(note.noteID);
if(updateNote.Document.canUserAccess())
{
//replace the values of the updateNote
//you can place this line in your base Repository class
context.Entry<Note>(updateNote).CurrentValues.SetValues(note); //I assume EF 4.1
}
}
I am trying to add a new comment to a comment table but all records in the table are being deleted with the exception of the one I added.
As an example: Let say I have an existing comment in the database for customer 1. I want to add a new comment.
In my controller I have the following:
List<CustomerComment> comments = _commentsRepository.CustomerComments.ToList();
CustomerComment newComment = new CustomerComment()
{
CustId = 1,
RevisionNumber = revNumber,
Comment = comment,
Customer = _commentRespository.GetCustById(1),
CommentDate = DateTime.Now,
UserId = 24,
Users = _commentsRepository.GetUserById(24)
};
comments.Add(newComment);
_commentsRepository.SaveComment();
In my repository I have the following:
public Int32 SaveComment(CustomerComment comment)
{
try
{
_DB.SubmitChanges();
}
catch
{
throw;
}
return comment.CommentId;
}
While stepping through I see no changes to the data until after I create the new comment and step into the SaveComment method. What is strange is that it shows the comments already in the table for Delete and the new comment for insert.
Not understanding why it thinks the existing comments should be deleted.
I have also tried InsertOnSubmit but it does the samething so I took it out.
One thing I have noticed is that the existing comment after loading in the controller (comments) has the customer object as null. When I create the new comment I am assigning the customer to the new comment (Customer = _commentRespository.GetCustById(1).) Is this causing the delete and why doesn't the object get created and assigned when loaded.
Some additional information is that I am using POCOs and an XML mapping file.
Maybe you should not add the comment to an in memory storage try adding the new comment to the data context instead. I am presuming in your repository you have the add method... So something like _commentsRepository.add(newComment); shoukd work...
Regardless of that, why are you storing the whole customer in the database and for that matter user? you should be storing only their ids no? when you need read onky data to be thrown out into the view you may require additional data such as the customer and user details, use a dto object for that. Persistance in one thing, viewing data with certain data possibly populated from various tables is another thing...
I'm working on an ASP.NET MVC project that will allow users to perform batch edits on the attributes of objects. The implementation is in a sort of "wizard" like form with four phases to the process as follows:
"Select the attributes you want to edit" - the first page will present the user with a list of checkboxes representing each of the attributes they want to edit. The user should check the attributes they wish to edit and select "Continue".
"Edit the selected attributes" - the second page will present the user with a list of distinct "editors" which will be unique for each of the attributes they selected on the first page.
"Review your changes" - this page will allow the user to review the changes they've made to the attributes they selected.
"Submit your changes" - this page will actually submit the information about the edits the user wishes to make to the selected attributes against the selected collection of objects.
Fairly straight-forward.
As I mentioned, the "editor" will be unique to each attribute, and could have any combination of different controls on it. Once a user has made their edits and the application posts that information to the "Review" page is where I'm currently having my problem.
We've developed the concept of an "EditorWorker" class that is unique to each attribute, which is responsible for generating the ViewModel necessary for each editor, but is also responsible for creating/returning (within the "Review" page controller action) an object that is the "model" object for the editor that the post data can be bound to, which can then be use to display the edited data for review. This object should have properties that match up with the IDs of the controls in the editor so that model binding can occur.
I've got the "EditorWorker" creating and returning the class needed, but for some reason, when I call TryUpdateModel and pass in that class, its properties aren't getting populated as a result of that method call as I would expect them to. I have verified that the values are in the posted FormCollection. Below is the code for my controller action where I'm attempting to do this. If someone can help me understand why TryUpdateModel isn't working in this scenario, I would be very appreciative.
[HttpPost]
public virtual ActionResult Review(ReviewBatchViewModel model)
{
var selectedAttributes = GetSelectedAttributes(model.SelectedAttributeIds.Split(',').Select(i => Int64.Parse(i)).ToArray());
var workers = new List<IEditorWorker>();
var reviewData = new Dictionary<ViewAttribute, IEditData>();
foreach (var attribute in selectedAttributes)
{
if (!string.IsNullOrEmpty(attribute.EditorWorker)) // If there is no EditorWorker defined for this object, move on...
{
var worker = ServiceLocator.Current.GetInstance(Type.GetType(string.Format("{0}.{1}", EditorWorkerNamespace, attribute.EditorWorker)));
var attributeEditData = ((IEditorWorker)worker).LoadEditData();
if (TryUpdateModel(attributeEditData))
model.EditData.Add(attributeEditData); // model.EditData is a List<IEditData> that will be iterated on the Review page
reviewData.Add(attribute, attributeEditData);
}
}
return View(model);
}
// ReviewBatchViewModel.cs
public class ReviewBatchViewModel : BaseViewModel
{
public ReviewBatchViewModel() { EditData = new List<IEditData>(); }
public string SelectedAttributeIds { get; set; }
public List<ViewAttribute> SelectedAttributes { get; set; }
public List<IEditData> EditData { get; set; }
}
// IEditData.cs
public interface IEditData
{
}
// BroadcastStatusEditData.cs
public class BroadcastStatusEditData : IEditData
{
public int BroadcastStatus { get; set; }
}
I totally understand that this controller action is incomplete in its current state. I'm presently working on just trying to get those EditData objects populated correctly before I move on. As mentioned, any thoughts would be greatly appreciated. Thanks.
UPDATE: With regards to #mare's comment, I should have explained that part more clearly, sorry. The call to TryUpdateModel actually is returning true, but the fields on the model object being passed into it aren't actually being populated from the values that have been confirmed present in the posted form data. The model object being passed into the call is not a List, its just a poco. The resulting, ultimately hopefully populated model object is then being added to a List collection of model objects that will then be used for displaying the posted data for review on the Review page. I'm not loading anything from a datastore at all. Unique editors for each selected attribute are being rendered to the Edit screen, and I'm attempting to capture the edit values for display on a Review screen prior to submitting the batch of edits to a service. Hopefully that's more clear. Thanks.
UPDATE 2: I've included the definition of the ReviewBatchViewModel class as requested by #mare in the comments. The use of the var keyword in most cases in this code sample is largely due to the fact that the methods that are populating those variables is going to be returning an object of a different type for each attribute selected, so I never know exactly what its going to be at runtime (although it will always implement an interface, in this case either IEditorWorker and/or IEditData). There is a single class in the Model called "Attribute". The provided code sample has three variables relative that class: 1) SelectedAttributeIds is a comma-separated list of the Id's of the attributes that the user has selected to edit, which gets passed from the Edit page to the Review page via hidden field, 2) selectedAttributes is a collection of the actual Attribute objects that correspond to those Ids that I can work with, and 3) attributeEditData is an instance of the IEditData class specific to each given attribute that I'm attempting to bind the posted data from the Edit page to.
Hopefully this additional information clears things up even more.
TryUpdateModel is a generic method, and therefore attempts to infer all type information based on the Generic Type Parameter.
From what I understand in your example above, you are always passing in a IEditData correct?
In effect you are saying:
TryUpdateModel<IEditData>(attributeEditData)
This is most likely the cause for not seeing any properties being set, since IEditData doesn't have any properties ;)
To do what you want you will probably have to create a custom ModelBinder.
As a quick code review side note, your solution seems overly complicated. I had to stare at your solution for a good while just to figure out where to start. Creating a custom model binder may solve your immediate problem, but you might be looking at a big time maintenance headache here. I'm willing to bet there is a simpler approach that will lead to fewer problems down the road.
Based on your comments I have changed the code around from System.Object to your IEditData interface, but everything still holds. I noticed in an earlier comment you mentioned using var because you didn't know the type until runtime. However, there is nothing magic about the var keyword. The only thing it does is give you implicit typing, but it is still statically typed.
The nice thing about MVC is that you can just pop over to Codeplex and have a look at the source for TryUpdateModel if you want. Digging down a few layers you will eventually find a call to this internal method:
protected internal bool TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties, IDictionary<string, ValueProviderResult> valueProvider) where TModel : class {
if (model == null) {
throw new ArgumentNullException("model");
}
//valueProvider is passed into this internal method by
// referencing the public ControlerBase.ValueProvider property
if (valueProvider == null) {
throw new ArgumentNullException("valueProvider");
}
Predicate<string> propertyFilter = propertyName => BindAttribute.IsPropertyAllowed(propertyName, includeProperties, excludeProperties);
//Binders is an internal property that can be replaced by
// referencing the static class ModelBinders.Binders
IModelBinder binder = Binders.GetBinder(typeof(TModel));
ModelBindingContext bindingContext = new ModelBindingContext() {
Model = model,
ModelName = prefix,
ModelState = ModelState,
ModelType = typeof(TModel),
PropertyFilter = propertyFilter,
ValueProvider = valueProvider
};
binder.BindModel(ControllerContext, bindingContext);
return ModelState.IsValid;
}
Notice the use of typeof(TModel) everywhere... in your case that is getting translated into typeof(IEditData), which isn't very useful since it is only a marker interface. You should be able to adapt this code for your own use, making sure to use GetType() in order to get the actual type at runtime.
I hope this helps out!
P.S. I've added some comments to the above code to help out a little
#Josh, you were very helpful in helping me understand why TryUpdateModel wasn't working for me, and I appreciate that. Unfortunately, I think the larger issue here was that fact that I (not exactly sure which) was either unable or unwilling to try to document all of the details of the requirements for the problem I'm trying to solve here, which I think made it difficult for anyone to be able to provide much meaningful input. The biggest problem for us is that, because we have no idea until runtime which attributes a user has selected for editing, we don't know which objects we'll be working with in the context of these controller actions, or what their types will be. The one place that we safely can work with known data and types, is within the context of each of the unique EditorWorker objects, which is where I've chosen to do the heavy lifting here.
I was hoping and attempting to take advantage of all of the heavy lifting that MSFT has done for us within the MVC framework to handle model binding, but I've come to the conclusion at this point that I don't think that's going to work for us. The solution that I've come up with at this point, is to allow the LoadEditData method of the EditorWorker classes handle loading up the EditData classes for for me. As each EditorWorker class is unique to, and has knowledge of the attribute that it is associated with. The problem I was having originally was that I was letting the EditorWorker.LoadEditData method just return an empty instance of the specific type of EditData class that I needed for the attribute I was currently working with, and let the MVC framework handle model binding to that object for me. That wasn't working because that method is designed to return an object of type IEditData, and I never really knew exactly what type it was that I was currently working with, so I had no way of specifying the type in the call to either of the typed methods: TryUpdateModel<T> or UpdateModel<T>.
So the solution I've come up with, and am going with at least for now (re-education and/or refactoring may very well change this in the future, who knows) is to just pass the Request.Form object into the call to EditorWorker.LoadEditData and let that method handle actually loading up the EditData object that it knows it needs to return for the attribute it's responsible for, which it can do as it knows what information should be in the posted form collection to load up its EditData object.
So that's where I'm at for now. Thanks for the help.