Skip to main content
Home Forums Silverlight Programming WCF RIA Services Having trouble inserting child (associated) entity
14 replies. Latest Post by trevors on November 4, 2009.
(0)
sapientc...
Member
222 points
69 Posts
06-09-2009 1:05 AM |
I have a parent entity called Company that has a child (associated) entity called Address (since a company can be tied to an address in the DB). I have the appropriate "Include" and "Association" attributes defined on the Address property in my Company.metadata.cs class so that RIA Services knows they're related. I know those attributes are working because I can call LoadCompanies() and get a list of companies with their addresses from the database.
However, when I go the other direction and try to add a new company to the database, I'm running into a problem. I have a simple routine on the client that constructs a Company, initializes its Address property with an empty Address entity, and then binds the company (and its address) to a DataForm. After a user fills out the form and clicks OK, I call the following code:
// Note: companyDds is defined earlier in the code and is // of type DomainDataSource. newCompanyWindow is a Silverlight // child window and is the container for the DataForm. CompanyContext companyContext = (CompanyContext)companyDds.DomainContext; companyContext.Companies.Add(newCompanyWindow.Company); companyDds.SubmitChanges();
When the call to SubmitChanges() executes, I get the following exception:
System.NotSupportedException: This entity list of type 'Address' does not support the Add operation.
I tried adding an AddressService on the server thinking that I might be getting the error because there was no mechanism in place for adding addresses to the database, but that didn't help. I'm still getting the error.
Any ideas what might cause this?
Thanks!
06-10-2009 2:03 AM |
I believe I may have figured out what's going on after using .NET Reflector to examine how RIA Services generates its client-side code.
It looks like whenever one entity refers to another (i.e. has a property or method that takes an associated entity as its type), both entities must have CRUD operations defined within a single DomainService class. In my case, I have a CompanyService class that contains CRUD operations for companies. As a result, RIA Services generates a CompanyContextEntityContainer class on the client that allows all company operations (EntityListOperations.All) but no address operations (EntityListOperations.None) -- hence the exception when trying to insert a company that also requires inserting a new address.
The only way I've been able to solve this is to add my address CRUD operations to the same DomainService as my company operations. That causes the RIA code generator to generate the CompanyContextEntityContainer on the client with EntityListOperations.All defined for addresses as well as companies.
I'm not sure I prefer this approach since employees (another entity type) can have addresses as well, which means I'll have to group companies, employees, and addresses all in a single DomainService in order for inserts to work properly.
Can anyone here confirm my findings? Do CRUD operations for related/associated entities need to be grouped within a single DomainService class?
ianicbass
6 points
6 Posts
09-23-2009 7:49 PM |
I am having the same issue. After creating those CRUD operations for my lookup entity, I am now getting an error whenever I insert a new record on my main entity. The error is: "Violation of PRIMARY KEY constraint 'PK_LookupTable'. Cannot insert duplicate key in object 'dbo.LookupTable'. The statement has been terminated." Has anyone resolved this? Please let me know. Thank you.
johnmcfet
23 points
23 Posts
10-05-2009 12:01 PM |
I am having the same issue and am questioning the value of the Ria services as a result. My situation is slightly different as my domainservice on the server side is in one file as the result of reading this thread(if this is a requirement then it is not a viable product as there are other problems in generating these files). I have a Product object that has associated child Address object (FK). and I have the [include] in the metadata file and my GetProducts looks like:
{
accountLoadOptions.LoadWith<
}
I iniitailly have an empty products and address table and have a DataForm on the client that looks like:
in my generated .g.cs file I have the following code for Product:
public
... skip the unecessary details
... skip rest
so the DomainContext is aware of the child relationship . I then have an event handler that created the product and address classes:
p.Address1 =
When I fill in the steet (Address1.Street) and hit the Ok button on the dataform the street field is reset to blank.This should be pretty fundamental stuff if the Domainservices are meant for real world apps. I love the concept here but it looks like the finished product is a not in sight
ColinBlair
Contributor
6741 points
1,318 Posts
10-05-2009 12:43 PM |
johnmcfet: private void DataForm_AddingNewItem(object sender, DataFormAddingNewItemEventArgs e) {Product p = new Product(); p.Address1 = new Address();(GreenData.DomainContext as GreenDomainContext).Products.Add(p); // GreenData.DomainContext.Entities. }
I am not sure what you are expecting this code to do. As far as I can tell, on the AddingNewItemEvent of the DataForm you are creating an empty Product, attaching an empty Address, and dumping the two into your DomainContext without setting any values. Are you doing something else with these later? I am not sure how any code is supposed to find these later, you didn't supply any key information are stuff a reference into a local variable or anything.
10-05-2009 2:05 PM |
Thanks for the quick reply, obviously I am off on the wrong track as i could find no example that handled this case. If I am creating a new object and its child in this case address . If someone could point me to an example it would be very helpful! I am thinking that I have to associate the new object as "currentitem". Also wondering if I am expecting too much a this stage in the product as I notice it is feature for future revs. Any assistance is appreciated
10-05-2009 2:58 PM |
Before I get to your problem, let me get on my soapbox for a minute.
Part of the problem is that you are looking at different things here and trying to equate them as a single whole. One is the DataForm which is part of the Silverlight Control Toolkit and was advertised as being part of Silverlight 3. It tends to be used in lots of RIA Services demos so it does get discussed a lot in this forum but, strictly speaking, it isn't part of RIA Services.
Next we have RIA Services itself which can itself be split into two categories. First we have the DomainDataSource or DDS. The DDS is the buggiest part of RIA Services and it is the opinion of most Silverlight gurus that the DDS is generally a bad idea and shouldn't be used even with the bugs fixed. As far as I know, there isn't anybody outside of Microsoft arguing otherwise. That leaves the Entity, EntityRef, EntityCollection, EntityList, EntityContainer, DomainContext, DomainClient, and DomainService as being the "real" RIA Services and those sections of RIA Services are pretty rock solid and you should set your expectations pretty high.
If you are architecting your system using best practices, meaning using MVVM instead of the DDS, then you should find that RIA Services is very stable. As far as I know the only bugs in RIA Services are at the codegen level. As long as you can get past those (generally, don't have the word Public in any of your properties and don't use VB.NET) then you are good.
OK, off the soapbox.When the DataForm is told to create new it will create the new Entity itself. However, that is happening after the event you are currently handling. You may want to check the other events like CurrentItemChanged or the different BeginEdits and check if the CurrentItem has a null address. If it does then create the Address then. I have to admit I am kind of guessing here and there might be a better way that I am unaware of.
For reference, here is how I would do it. I would have a ViewModel with a CreateNewProduct command on it bound to the create new button on screen. That command would create the new Product and Address entities and then set the Product entity to a property on the ViewModel named CurrentProduct. The CurrentItem property of the DataForm would be bound to that property.
10-05-2009 3:32 PM |
Thanks alot, I would be using MVVM and actually Prism/CAL in my main application but this is standalone wizard that RIA seems ideal for. I must admit that I am now looking at IdeaBlade. Anyways I tried the following :
at this point the address is still null ! RIA understands the relationship to address but does not new the object. anyways I did by adding this:
however no glory , but am I wrong in thinking that RIA should do this for me. I should not need to do this.
10-05-2009 3:45 PM |
10-05-2009 5:33 PM |
Ward makes some good software but if you hooked up DevForce to your DataForm I am pretty sure the result would be the same. This is fundamentally a DataForm problem and not a RIA Services problem. I am really not the right person to help with DataForm problems and hopefully somebody else has an answer for you. As I said I don't use the DataForm. I do Prism/CAL/MVVM using RIA Services as the data layer, an arrangement that I find far superior to either WCF or ADO.NET Data Services
To answer your question at the end, no you shouldn't expect RIA Services to automatically create the address for you. That is not the kind of decision that your data layer should be making automatically. You can expect the DataForm to create the Address for you, but that isn't RIA Services.
10-06-2009 10:35 AM |
Thanks for the help I will zoom in on the dataform issues
WardB
18 points
7 Posts
10-06-2009 7:16 PM |
Thanks for the lead-in Colin. This is Ward Bell of IdeaBlade. I can't pronounce on RIAS but, since you asked about IdeaBlade's DevForce, let me see if I can offer some clues.
First, Colin is correct that it is important to separate the DataForm behavior from your expectations about how the Model should behave. That's one reason so many of us are adamant about moving business and persistence related logic out of the UI layers.
Second, you shouldn't suppose that the DataForm - or any UI element for that matter - will create the Address for your Product.
Third, it is unwise to suppose that any business layer infrastructure will know your business rules for constructing entities.
These are all reasons that contribute to one of my rules of thumb: avoid "new"ing an entity unless it's the simplest, dumbest entity imaginable (and I admit there are often plenty of those).
In your case, you have a business rule that says every Product has an Address. In some circles, people would say that Product is an "aggregate" that contains supporting entities (e.g., Address) which belong to that aggregate Product. The supporting Address entity should have no independent existence from its parent Product and there you shouldn't expose a way to acquire the address except by navigating from Product to Address (e.g., "p.Address").
You know this rule. The infrastructure (DevForce or RIAS) cannot know that rule. The presence of an association between Product and Address is not sufficient to tell the framework that it should create an Address when we create a Product.
You might argue "Address is non-nullable in Product; therefore the framework should know to make that address for me whenever I create a new product." I agree that the framework knows there must be an address. We expect the framework to detect that if you try to save the product without an address. However, the framework can't know where that address comes from nor how it should be made. Could it be an existing address? If the address should be new, should it be filled-in somehow? Are there constraints on Address creation? The framework can't know.
What does all of this mean to you? I think it means that you should strongly consider making a ProductFactory class that produces new, properly configured Products. Given such a factory you would write "productFactory.CreateProduct()" and out would come a properly constructed Product, complete with a new associated Address, ready to be added to your Data Context (or DevForce EntityManager equivalent).
Inside that factory is language such as:
var prod = new Product();this.context.Add(prod); // "context" is an EntityManager in DevForcevar addr = new Address();prod.Address = addr;return prod;
var prod = new Product();
this.context.Add(prod); // "context" is an EntityManager in DevForce
var addr = new Address();
prod.Address = addr;
return prod;
Like Colin, I have no idea if this particular Product instantiation code makes any sense in your application. You aren't setting any values on either the Product or the Address. There is nothing to distinguish one newly created Product from the next ... except for the product's entity key.
Your factory method could set some reasonable defaults to make the new product easier to find; perhaps the individual entity constructors provide some defaults for each new entity ... we can't tell at this distance. But I do know that we can encapsulate the creation of a new Product and Address in this manner and that the new Address now belongs to the new Product.
This is the kind of advice I would give to you whether you were writing in RIAS or DevForce. The exact details would differ between the two but the pattern is much the same. Your take away should be:
a) you are responsible for defining how a new aggregate object graph is builtb) you would be wise to hide the messy details in a product factory so someone looking at your AddNewItem implementation (wherever it resides) would see only the expression of your intention: "Product p = productFactory.CreateProduct(); "
a) you are responsible for defining how a new aggregate object graph is built
b) you would be wise to hide the messy details in a product factory so someone looking at your AddNewItem implementation (wherever it resides) would see only the expression of your intention: "Product p = productFactory.CreateProduct(); "
Finally, as you move toward MVVP, you'll want to get this creation code out of the code-behind. The first stop is the ViewModel; think seriously, as a next step, about moving "Product p = productFactory.CreateProduct()" inside a command implemented there. I realize this feels like a game of hide-the-pea. It makes little sense when you're starting. But as the application gets bigger and you want to start testing how new Products are made, you'll appreciate that you put it in a factory and that you exercised that factory in a ViewModel that you can test independently of the visuals.
Hope this helps.
10-07-2009 3:08 PM |
gee I am very impressed by the response, Ward. I totally agree on the MVVM usage which I use in main app which is using CAL with MVVM. this started out to be a simple installation wizard and as such I did not use MVVM.
I understand what u are saying about the product and Address relationship but I must point out that in the binding of the XAML I use the addess.name to get a value from the child address object so I figured the Dataform could reflect on the product and finding the Address create it. I have written my own Dataform (not hard) and in the add new item I have the following code:
product = new Product();
product.Address1 = new Address();
((IEditableObject)product).BeginEdit();
LayoutRoot.DataContext = product;
Of course this works fine because I know my object hierachy. I guess I am asking too much for a UI control to be able to do the same.
So I guess I would have had the same problem with your product which by the way would not install on my Windows 7 RTM machine. I love the ideas that are presented by you and RIA and would like to make all of this work seemlessly using standard controls. I would make a great subject for one of Code camp presentions. Thanks
http://www.sandkeysoftware.com/Silverlight/ArticleNavigator/ArticleNavigator.Web/ArticleNavigatorTestPage.html
10-07-2009 5:08 PM |
Hi John - Sorry about the install stumble; please contact our support so we can find out what went wrong (works fine on Win 7 of course).
The reason I'm back with an update is that I've since discovered that you would not have been able to control the adding of your entity graph within the DataForm in any case. It appears there is no easy way to take control of how the DataForm adds new items. It seems the DataForm will either new up the item or, if the bound collection implements IEditableCollectionView, it will call the collection's AddNew method. Unless you own that collection, you can't determine how it creates the item either. This quandry is being pursued elsewhere.
Meanwhile, many are doing as you have done ... and replacing the DataForm with their own model-aware views.
I trust you understand my aversion to the notion that anything should create new entities on the fly without specific knowledge of the business requirements.
Come on back to DevForce when you're ready.
trevors
2 points
1 Posts
11-04-2009 2:12 PM |
Guys, I am not sure (speed read your issue) if you are having the same problem I had and solved; but if it is simply that you get blown out when submiting changes on an entity with children to which you have not assigned a child, the failure is due to the child being null. I resolved this by add to the insert in the context and creating a child eg
I don't know if this helps or if it is recomended, as I have only been looking at ria for a couple of weeks, but it works.
(OID is key fields)
employee.OID = employee.People.OID;