Inserting an entity and dependent entities using RIA Services - silverlight

I got a question about inserting an entity with dependent entities using RIA Services (With Silverlight 4.0).
Let's say I have an Entity in my (sql) database called "Beer" and one called "Supplier", with a relationship: Beer 1 - n Supplier. There are multiple suppliers for one kind of beer.
Now there's the following use case: The user enters a new beer with, let's say, 5 suppliers.
On the silverlight view I now got two DomainDataSource's. On the Beer DomainDataSource I add and submit the new beer and on the Supplier DomainDataSource I submit the now suppliers, which contain a foreign key which links them to the beer.
My question is: How can I make sure that the Beer gets submitted first and afterwards the dependent (remember the foreign key) Suppliers?
I am aware that I could simply chain up the SubmitChanges() using the OnSubmitted event. But this solution is pretty... well... lame. It makes for some really ugly code.
Thanks for all your numerous ideas!

Unfortunately there is no way to force the order of the updates that come in the same ChangeSet.
However, if all new suppliers are submitted to the server with new beers (a big IF), you could manually check the ChangeSet in your Upddate method:
public void UpdateBeer(Beer beer)
{
foreach(ChangeSetEntry changeSetEntry in ChangeSet.Entries)
{
if (changeSetEntry.Entity.GetType() == typeof(Supplier))
{
Supplier supplier = (Supplier)changeSetEntry.Entity;
UpdateSupplierInternal(supplier);
}
}
DataContext.Beers.Attach(beer, ChangeSet.GetOriginal(beer));
}
That calls a separate method to update supplier. You still need an UpdateSupplier method or RIA will throw an exception when it exists in the ChangeSet, but the method should do nothing:
public void UpdateSupplier(Supplier supplier)
{
// do nothing
}

Related

primary key constraint updating many-to-many self referential table in entity framework code-first

TL;DR What is the proper way of rehydrating an entity framework object with a self referential many to many relationship from a DTO and updating it with the new values so that the database updates correctly?
I have the following entity (irrelevant stuff trimmed)
public class Role
{
[Key]
[Required]
public String RoleId { get; set; }
public List<Role> Children { get; set; }
}
In my dbContext, I have set up a many to many relationship
modelBuilder.Entity<Role>().HasMany(r => r.Children).WithMany();
I'm using MVC front end, with a web-api backend for an n-tier setup, and an mssql database.
The following chain of events happens
Browser->MVC Controller->REST call to Web API->WebAPI Controller->DB Context Query
This chain happens twice, once to view the page in edit mode, and then again when the user pushes the save button to persist.
When setting children on the entity, they always already exist first (IE, you don't create the parent and the children at the same time, you are just adding an existing child to a parent)
There is a DTO used by the MVC model and web API, which I re-hydrate to the entity on the web-api side.
public IHttpActionResult UpdateRoleInfo(RoleVM roleInfo){
//lookup existing entity to update
var existing = db.Roles.FirstOrDefault(y => y.RoleId == roleInfo.ExistingRoleId);
...Something happens here (see below for things i've tried)...
db.SaveChanges();
}
My first try was this :
existing.Children = roleInfo.Children
This tried to recreate all of the existing children as part of the save. (Primary key constraint violation on the roles table)
I changed that to
//Fetch all of the roles from the database to lookup the existing children
var allRoles = GetRoles();
//Have to reselect the roles from the DB so the DB doesn't try to recreate new ones for the children.
var childrenToAdd = roleInfo.Roles.Select(role2 => allRoles.FirstOrDefault(r => r.RoleId == role2.RoleId)).ToList();
existing.Children = childrenToAdd;
This correctly works for updating a role that does not already have any children, to add some the first time, but if you update a role that already has children, it tries to re-add the children to the database a second time, getting a primary key violation on the roles_role table
I then tried pre-pending this code to the second one above,
existing.Children.Clear();
db.SaveChanges();
I would expect this to delete all the existing parent-child relationships from the many to many table for this parent, and then recreate them with the new children. Why not?
TL;DR What is the proper way of rehydrating an entity framework object with a self referential many to many relationship from a DTO and updating it with the new values so that the database updates correctly?
Try turning off auto detect changes (before retrieving from the DB) via
context.Configuration.AutoDetectChangesEnabled = false;
Then set the state to modified on the specific role object you are updating
context.Entry(role).State = EntityState.Modified;
Haven't tried this myself on a self-referencing many-to-many table, but adding & updating entities in the manner can save all sorts of headaches where EF incorrectly infers what you are adding/updating
Found the problem.
On the initial load of the entity, I was using an include statement to eager load the children.
When I updated the entity, when I fetched it from the db again, I did not eager load the children. Therefore the additions/updates were getting confused. Once I put the include in during the upload Scenario #2 above worked (the explicit clear was not needed)
db.Roles.Include("Children").FirstOrDefault(z => z.RoleId == RoleId);
Also related, if you have this same problem when dealing with relationships across different tables, make sure all the entities that are involved in the graph are from the same DB context!
https://msdn.microsoft.com/en-us/magazine/dn166926.aspx

Silverlight + RIA: problem editing entity

I have a Silverlight 4 application with EntityFramework as data layer.
There are two entities: Customer and Products. When I get customer from a database, the related products are also read, as I added related 'Include' attribute in customer's metadata and call Include method in get query:
public IQueryable<customer> GetCustomerSetById(int customerId)
{
return this.ObjectContext.CustomerSet
.Include(o => o.Products)
.Where(o => o.Id = customerId);
}
The problem that when I change any property in customer's product I get this exception:
This EntitySet of Type
'MyApp.Web.Models.Product' does not
support the 'Edit' operation.
But everything works if I read customer products directly, e.g. not through customer entity (CustomerContext) , but via product one (ProductContext).
Also there is the IsReadOnly=true property in a product entity.
UPDATE:
I have all CUD operations and also marked all of them with related Insert, Update and Delete attributes. Otherwise it wouldn't work at all, but it works for me in some cases as I wrote above.
Any ideas?
This is the real problem with RIA+EF so we keep all our entities in one domain service because at client side it is difficult to deal with multiple entities related via navigation properties. Think for a minute it actually makes no difference and we use EF T4 template to generate all domain service operation in one class. And we generated partial methods to intercept logic of domain service methods.
It sounds like you need to make sure you have an update operation in your domain service. It will look something like this:
public void UpdateProduct(Product product)
{
ObjectContext.Products.AttachAsModified(product, ChangeSet.GetOriginal(product));
}
RIA Services EntitySet does not support 'Edit' operation
Since the aforementioned solutions do not appear to be helping try using this:
Domain Service Wizard
This wizard should look at your entity, and generate the appropriate CRUD operations.
If you then cant update your entities you have a different problem.
Have you tried moving the Include to the end?
Return this.ObjectContext.CustomerSet
.Include(o => o.Products)
.Where(o => o.Id = customerId);
Could be:
Return (from o in this.ObjectContext.CustomerSet
where o.Id = customerId
select o).Include("Products");

SL4 / EF / RIA Services - Return parents, grand parents and great grandparents in one server hit

Let's say I have Category > SubCategory > SubSubCategory > Item set up in my EF entities.
What is the best way to get Category, Subcategory, SubSubCategory and Item where Item.Property = x all in one single request to the server using WCF RIA Services?
With .Include I can only get the children of the entity, not grandchildren and further down ( or up depending on how you look at it).
Furthermore, if I do this...
public IQueryable<ToolingTreeItem> GetTree(int currentLocationId)
{
var tree = from tc in this.ObjectContext.ToolingCategories
from tg in tc.ToolingGroups
from tt in tg.ToolingTypes
from t in tt.Toolings
where t.CurrentLocationId == currentLocationId
select new ToolingTreeItem { Cat = tc, Group = tg, Type = tt, Tool = t };
return tree;
}
...the method is not available on my context in the client side project, presumably because my custom entity class ToolingTreeItem is not recognized somewhere in the mysteries of the deep chasm that is WCF RIA Services.
If it isn't obvious by now, all I want to do is populate my TreeView with Category > SubCategory > SubSubCategory > Item in a single call to the server. What is the best approach?
Many happy returns!
You should be able to load the entities by using eager loading.
Assuming that you add "[Include]" to the "parent" attributes in you metadata something similar to the code below should work (note that I have guessed the name of all relations so you will probably need to edit the code)
public IQueryable<Toolings> GetToolsWithTree(int currentLocationId)
{
var tree = from t in this.ObjectContext.Tooling.Include("ToolingType.ToolingGroup.ToolingCategory")
where t.CurrentLocationId == currentLocationId
select t;
return tree;
}
It looks like ToolingTreeItem is a complex object, rather than an entity. Unfortunately, RIA services can't generate classes on the client side that are a mix of complex objects and entities - the class has to be entirely one or the other. The two solutions that I'm aware of are to make the ToolingTreeItem an 'entity', by putting the Key attribute on a property, or just make several requests for the data. I've also found this a real limitation.

JSF - How to use database row values to generate additional columns in datatable

I have a problem which should be easy but I have trouble with finding the solution. I have a database that I want to present in a datatable. However I need additional columns that get more information about the current row. For example - the database has a set of people with id numbers. What I need is to display the Name, Last Name, ID (all columns of database), but then I want to display the address which is in a different REST webservice and to get it I need to send the ID (from database). I hope I have described it clearly but just in case I will point out:
-my database has 3 columns: name, las name, id number
-I need to add a 4th column to my datatable with address
-to get the address I need to send the id number to a rest webservice
The only solution I was able to find so far i to add all database elements to a list or some container and then use it inside a bean. But that is unacceptable since the database is very big. There must be a simpler way but it seems that I can't form an adequate question for google to get the proper results :> Can any one give some advice?
So what you basically want is to do a join between the result of a query to a database and the webservice.
Of course you don't need to load the entire DB in some bean to do this "join", just load the data you would like to display on screen in a backing bean, load the addresses for each row, and bind the result to the datatable.
Something like this:
#ManagedBean
#ViewScoped
public class MyBean {
#EJB
private someDataService;
#EJB
private someWebService;
List<Person> persons;
Map<Long, String> addresses;
#PostConstruct
public void retrieveData() {
persons = someDataService.getByID(someID);
for (Person person : persons) {
addresses.put(person.getID(), someWebService.getByID(person.getID));
}
}
// getters here
}
And the Facelet:
<h:dataTable value="#{myBean.persons}" var="person">
<h:column>
#{person.name}
</h:column>
<h:column>
#{person.lastName}
</h:column>
<h:column>
#{myBean.addresses[person.ID]}
</h:column>
</h:dataTable>
There are some choices to be made. You could also do the joining inside a single service and have it return some type that includes the address. You could also make a wrapper or a decorator.
It would also be more efficient if the web service could handle a list of IDs instead of requiring a separate call for each address fetched. You might also wanna do some local caching, etc, but those details are up to you ;)

How to get can CanAddNew to be true for a collection returned by RIA Services

RIA Services is returning a list of Entities that won't allow me to add new items. Here are what I believe to be the pertinent details:
I'm using the released versions of Silverlight 4 and RIA Services 1.0 from mid-April of 2010.
I have a DomainService with a query method that returns List<ParentObject>.
ParentObject includes a property called "Children" that is defined as List<ChildObject>.
In the DomainService I have defined CRUD methods for ParentObject with appropriate attributes for the Query, Delete, Insert, and Update functions.
The ParentObject class has an Id property marked with the [Key] attribute. It also has the "Children" property marked with the attributes [Include], [Composition], and [Association("Parent_Child", "Id",
"ParentId")].
The ChildObject class has an Id marked with the [Key] attribute as well as a foreign key, "ParentId", that contains the Id of the parent.
On the client side, data is successfully returned and I assign the results of the query to a PagedCollectionView like this:
_pagedCollectionView = new PagedCollectionView(loadOperation.Entities);
When I try to add a new ParentObject to the PagedCollectionView like this:
ParentObject newParentObject = (ParentObject)_pagedCollectionView.AddNew();
I get the following error:
" 'Add New' is not allowed for this view."
On further investigation, I found that _pagedCollectionView.CanAddNew is "false" and cannot be changed because the property is read-only.
I need to be able to add and edit ParentObjects (with their related children, of course) to the PagedCollectionView. What do I need to do?
I was just playing around with a solution yesterday and feel pretty good about how it works. The reason you can't add is the source collection (op.Entities) is read-only. However, even if you could add to the collection, you'd still want to be adding to the EntitySet as well. I created a intermediate collection that takes care of both these things for me.
public class EntityList<T> : ObservableCollection<T> where T : Entity
{
private EntitySet<T> _entitySet;
public EntityList(IEnumerable<T> source, EntitySet<T> entitySet)
: base(source)
{
if (entitySet == null)
{
throw new ArgumentNullException("entitySet");
}
this._entitySet = entitySet;
}
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
if (!this._entitySet.Contains(item))
{
this._entitySet.Add(item);
}
}
protected override void RemoveItem(int index)
{
T item = this[index];
base.RemoveItem(index);
if (this._entitySet.Contains(item))
{
this._entitySet.Remove(item);
}
}
}
Then, I use it in code like this.
dataGrid.ItemsSource = new EntityList<Entity1>(op.Entities, context.Entity1s);
The only caveat is this collection does not actively update off the EntitySet. If you were binding to op.Entities, though, I assume that's what you'd expect.
[Edit]
A second caveat is this type is designed for binding. For full use of the available List operation (Clear, etc), you'd need to override a few of the other methods to write-though as well.
I'm planning to put together a post that explains this a little more in-depth, but for now, I hope this is enough.
Kyle
Here's a workaround which I am using:
Instead of using the AddNew, on your DomainContext you can retrieve an EntitySet<T> by saying Context.EntityNamePlural (ie: Context.Users = EntitySet<User> )
You can add a new entity to that EntitySet by calling Add() and then Context.SubmitChanges() to send it to the DB. To reflect the changes on the client you will need to Reload (Context.Load())
I just made this work about 15mins ago after having no luck with the PCV so I am sure it could be made to work better, but hopefully this will get you moving forward.
For my particular situation, I believe the best fit is this (Your Mileage May Vary):
Use a PagedCollectionView (PCV) as a wrapper around the context.EntityNamePlural (in my case, context.ParentObjects) which is an EntitySet. (Using loadOperation.Entities doesn't work for me because it is always read-only.)
_pagedCollectionView = new PagedCollectionView(context.ParentObjects);
Then bind to the PCV, but perform add/delete directly against the context.EntityNamePlural EntitySet. The PCV automatically syncs to the changes done to the underlying EntitySet so this approach means I don't need to worry about sync issues.
context.ParentObjects.Add();
(The reason for performing add/delete directly against the EntitySet instead of using the PCV is that PCV's implementation of IEditableCollectionView is incompatible with EntitySet causing IEditableCollectionView.CanAddNew to be "false" even though the underlying EntitySet supports this function.)
I think Kyle McClellan's approach (see his answer) may be preferred by some because it encapsulates the changes to the EntitySet, but I found that for my purposes it was unneccessary to add the ObservableCollection wrapper around loadOperation.Entities.
Many thanks to to Dallas Kinzel for his tips along the way!

Resources