What is the right way to append entity from one manager to another in breeze? - breeze

What is the right way to append entity from one manager to another?
Straight forward attempts result in overwriting entities or in warning about the same entity key...
var entity = em1.getEntities()[0];
em1.deattachEntity(entity);
// assume em2 already has entities
em2.attachEntity(entity);
I believe there should be some in-build functionality for appending entries to another non empty manager or for generating an unique key for selected manager.
Any suggestions?
UPDATE:
I did read the documentation and tried to do it via exportEntities and exportEntities:
var entity = em1.getEntities()[0];
var export = em1.exportEntities([entity], false);
//here em2 already has entity with the same key as entity that I want to add
var import = em2.importEntities(export, { mergeStrategy: breeze.MergeStrategy.Disallowed });
This gives me an error: A MergeStrategy of 'Disallowed' prevents Picture:#Macaw.Whitelabel.WebAPI.Models--1 from being merged
I really don't understand how to append entities....
UPDATE2:
I did discover that manually assigning id of the attached entity solves the problem and error disappears.
Is there a way to make it not manually but using breeze?

Related

Child navigation properties missing in imported entities in custom initializer

I have a custom entity definition like:
var Card = function () {};
var cardInitializer = function (card) {
// card.fields is defined in the metadata.
// card._cfields is an in-memory only field
// that breeze will not, and should not, track.
// Thus it is being added in the initializer
card._cfields = card.fields.slice();
};
When the data loads from the server everything is fine. The card.fields array has the corresponding data.
EDITED: Added more info and code of how manager is being set up
But when the data is round-tripped in local storage via .exportEntities and importEntities, the child data defined in the metadata, represented by the property card.fields in this example, is not loaded (the Array has length 0) during the initializer call, though it is subsequently available on the entity after load has completed.
Here is how the manager is being initialized:
var metadataStore = new breeze.MetadataStore();
metadataStore.importMetadata(options.metadata);
var queryOptions = new breeze.QueryOptions( {
fetchStrategy: breeze.FetchStrategy.FromLocalCache
});
var dataService = new breeze.DataService({
serviceName: "none",
hasServerMetadata: false
});
manager = new breeze.EntityManager({
dataService: dataService,
metadataStore: metadataStore,
queryOptions: queryOptions
});
entityExtensions.registerExtensions(manager, breeze);
var entities = localStorage[storage];
if(entities && entities !== 'null'){
manager.importEntities(entities);
}
Wow. You ask for free support from the harried developer of a free OSS product that you presumably value and then you shit on him because you think he was being flippant? And downgrade his answer.
Could you have responded more generously. Perhaps you might recognize that your question was a bit unclear. I guess that occurred to you because you edited your question such that I can see what you're driving at.
Two suggestions for next time. (1) Be nice. (2) Provide a running code sample that illustrates your issue.
I'll meet you half way. I wrote a plunker that I believe demonstrates your complaint.
It shows that the navigation properties may not be wired up when importEntities calls an initializer even though the related entities are in cache.
They do appear to be wired up during query result processing when the initializer is called.
I cannot explain why they are different in this respect. I will ask.
My personal preference is to be consistent and to have the entities wired up. But it may be that there are good reasons why we don't do that or why it is indeterminate even when processing query results. I'll try to get an answer as I said.
Meanwhile, you'll have to work around this ... which you can do by processing the values returned from the import:
var imported = em2.importEntities(exported);
FWIW, the documentation is silent on this question.
Look at the "Extending Entities" documentation topic again.
You will see that, by design, breeze does not know about any properties created in an initializer and therefore ignores such properties during serialization such as entity export. This is a feature not a limitation.
If you want breeze to "know" about an unmapped property you must define it in the entity constructor (Card)... even if you later populate it in the initialized function.
Again, best to look at the docs and at examples before setting out on your own.

Determine which entity properties have been modified in BeforeEntitySave

Using a custom EFContextProvider, I want to check which properties have been modified on an entity before it saves, so that I can implement:
Security: The client has permission to change only certain properties of an entity.
Auditing: Whenever certain properties are changed, the change needs to be logged.
There are suggestions on SO to use OriginalValuesMap to determine the modified properties, see here and here. If the original value differs from the new value, the property has been modified. However, these original values are supplied by the client, and thus can be forged to match the new values, bypassing this check.
The first SO question I linked suggests this is not an issue, because if the original values are forged in such a way, those properties won't be saved anyway:
For any other "unchanged" property, which we are not using in any way, we don't need to worry if it has been tampered with because, even if it has, the tampered value will not be persisted to the database
This is untrue however, as long as all modified properties on the entity have their original values forged. For example, the following code will bypass server-side security checks based on OriginalValuesMap and still save to the database:
manager.fetchEntityByKey('Employee', 42).then(function (result) {
var employee = result.entity;
employee.Salary(1000000); // do you think HR will notice?
delete employee.entityAspect.originalValues.Salary;
return manager.saveChanges();
});
When Breeze .NET receives the entity, it adds the entity to an Entity Framework context in Modified state, and with no properties marked as modified, Entity Framework's behaviour is to save all the supplied property values to the database.
IMO this is a security bug in EFContextProvider.HandleModified, where it overrides the EF entity state to Modified (there is even a comment in that method warning not to do so). In any case, what is the correct way to determine which properties have changed and are about to be saved?
In your Context intercept Save and check if it is legal save or not. For the sake of explanation, let's say you want to save entity of type RestrictedClass and you defined table RestrictedClasses which imitates table in your database.
public override int SaveChanges()
{
foreach (
var entry in
this.ChangeTracker.Entries()
.Where((e => (e.State == (EntityState) Breeze.WebApi.EntityState.Modified))))
{
if (entry.Entity.GetType() == typeof(RestrictedClass))
{
var entity = entry.Entity as RestrictedClass;
var originalEntities = RestrictedClasses.Where(e => e.Id = entity.Id).toList();
if (originalEntities.Count == 0) continue; // user is trying to add, illegal since it says it's modified, you do different check for EntityState.Added
var originalEntity = originalEntities[0]; // there should be only one, unique ID
//.... now you check differences between entity and originalEntity and decide whether it's legal or not based on user role.

Deleting and then adding a new identical entity throws an error

I have a model called a DeviceAccount. It is a join table that allows me to create many to many relationships.
I have a function that creates a new DeviceAccount by handing it an account & a device to join. See here:
var createDeviceAccount = function (account, device) {
var initialValues = {
account: account,
device: device
};
return manager.createEntity(entityNames.deviceAccount, initialValues);
};
I have a function to delete a DeviceAccount. See here:
var deleteDeviceAccount = function (account, device) {
var baseQuery = entityQuery.from('DeviceAccounts');
var p1 = new breeze.Predicate('device', 'eq', device);
var p2 = new breeze.Predicate("account", "eq", account);
var modQuery = baseQuery.where(p1.and(p2));
var results = manager.executeQueryLocally(modQuery);
results[0].entityAspect.setDeleted();
};
If I locally create, remove, create, remove the same device/account pair there is no problem.
If I take a device/account pair that exists on the server I can remove it fine, but when I add it again I recieve the following error:
Uncaught Error: This key is already attached:
DeviceAccount:#Test.Models-5:::5
If I follow this in more depth I can see that removing a local device changes the entityState to be 'Detached' and if I remove a device that also exists on the server its entityState gets changed to be 'Deleted'. I can't follow much further than this and I was hoping someone could explain why this could be happening?
Just to be clear, deleting an entity via entityAspect.setDeleted causes its entityState to be set to "Deleted". This action marks the entity for deletion on the next save and also removes it from any navigation collections on the client. The entity is still being tracked by the EntityManager after this operation.
In contrast, detaching an entity via entityAspect.setDetached removes it from the entityManager cache completely. This also removes the entity from any navigation collections on the client, but will have NO effect on the server during an EntityManager.saveChanges call, because the EntityManager no longer "knows" about the entity. Think of "detaching" as telling the EntityManager to completely forget about an entity, as if it had never been queried in the first place.
"Deleting" an entity followed by "re-adding" the same entity is problematic because this would cause the EntityManager to have two incarnations of the same entity; a deleted version and an added version. Therefore the EntityManager throws the exception that you are seeing.
I think what you want to do is delete and add a "new" clone entity with a different id.
Hope this makes sense!
The reason this happens is that Breeze is keeping track of that entity until you have fully removed it from the server to keep you from creating a new entity with the same ID, which of course will throw a server exception since you can't do that.
If you called saveChanges() on your entityManager before you tried to recreate it, then Breeze will go out to the server, remove the entity from the DB, return the promise, and completely detach the entity from the local cache since it no longer exists on the server.
You could set the entityState to detached manually, but then if you try to saveChanges and that ID already exists on the server it will throw an error.
Best Option
Pass the entity into the saveChanges method in an array -
results[0].entityAspect.setDeleted();
manager.saveChanges([results[0]]).then(saveSucceeded);
function saveSucceeded() {
console.log('Entity removed from server');
}
Now after saveSucceeded has completed you can create a new entity with that ID

Cannot insert new Employee entity using InsertOnSubmit()

I'm facing this exception An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported. when I try to insert a new entity into my Employees table (the master one).
There is a relationship between the master Employees table and the details Orders table, and I'm sure that the relationship between these two tables (and specifically Employee.Orders EntitySet) is the cause of the problem since when I removed the relationship, it returns back to insert into Employees table with no problems.
When I searched for the problem, there was this blog post which I tried to implement but my case is a different than the one in the blog post in these items:
He faces the exception when tries to update (while I try to insert).
The tables architecture is different.
how can I solve this problem?
Here's the insertion code:
Employee emp = new Employee();
emp.Name = empName; // empName is a local variable
// What should I default emp.Orders to?
dc.Employees.InsertOnSubmit(emp);
dc.SubmitChanges();
P.S: My DataContext is defined on class-level in my repository and the exception is being thrown when I call dc.SubmitChanges();. and I didn't Attach any object why does it say that?
Here is an article explaining what you need to do using the Attach and Detach methods:
http://www.codeproject.com/KB/linq/linq-to-sql-detach.aspx
I am guessing it is trying to save something else besides just the employee object or you aren't showing us the full code in your repository. When you instantiate your DataContext object (dc) try setting DeferredLoadingEnabled = false, and ObjectTrackingEnabled = false and see if it works. If it does, try watching the SQL code in SQL Server Profiler and see if it is modifying other objects that may have came from a different context like the message says.
var dc = new MyDataContext()
{
DeferredLoadingEnabled = false,
ObjectTrackingEnabled = false
};
My bet is on the primary key.
Are you sure the primary key is also set on auto increment?
Did you
try changing the name, does it work then?
What happens if you remove
all rows from your DB? can you insert one then?

Setting a collection of related entities in the correct way in EF4 using POCO's (src is the DB)

I have a POCO entity Report with a collection of a related POCO entity Reference. When creating a Report I get an ICollection<int> of ids. I use this collection to query the reference repository to get an ICollection<Reference> like so:
from r in referencesRepository.References
where viewModel.ReferenceIds.Contains(r.Id)
select r
I would like to connect the collection straight to Report like so:
report.References = from r in referencesRepository.References
where viewModel.ReferenceIds.Contains(r.Id)
select r;
This doesn't work because References is an ICollection and the result is an IEnumerable. I can do ToList(), but I think I will then load all of the references into memory. There also is no AddRange() function.
I would like to be able to do this without loading them into memory.
My question is very similar to this one. There, the only solution was to loop through the items and add them one by one. Except in this question the list of references does not come from the database (which seemed to matter). In my case, the collection does come from the database. So I hope that it is somehow possible.
Thanks in advance.
When working with entity framework you must load objects into memory if you want to work with them so basically you can do something like this:
report.References = (from r in referencesRepository.References
where viewModel.ReferenceIds.Contains(r.Id)
select r).ToList();
Other approach is using dummy objects but it can cause other problems. Dummy object is new instance of Reference object which have only Id set to PK of existing object in DB - it will act like that existing object. The problem is that when you add Report object to context you must manually set each instance of Reference in ObjectStateManager to Unchanged state otherwise it will insert it to DB.
report.References = viewModel.ReferenceIds.Select(i => new Reference { Id = i }).ToList();
// later in Report repository
context.Reports.AddObject(report);
foreach (var reference in report.References)
{
context.ObjectStateManager.ChangeObjectState(reference, EntityState.Unchanged);
}

Resources