I work on a project with Entity Framework Code First + Durandal + Breeze.
I have these entities models:
public class Packing
{
[Key]
public int Id { get; set; }
public string PackingDescription { get; set; }
...
public virtual List<Isotope> Isotopes { get; set; }
public virtual List<PhysicalForm> PhysicalForms { get; set; }
public virtual List<ChemicalForm> ChemicalForms { get; set; }
}
public class Isotope
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
...
public int PackingId { get; set; }
public virtual Packing Packing { get; set; }
}
public class ChemicalForm
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
...
public int PackingId { get; set; }
public virtual Packing Packing { get; set; }
}
public class PhysicalForm
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
...
public int PackingId { get; set; }
public virtual Packing Packing { get; set; }
}
When I run my project my Entity Framework Code First database is created.
First, I test the cascade delete 'by hand' directly in my database. When I have one Packing with multiple Isotopes and I delete the Packing, all cascaded Isotopes are deleted. This is ok for me.
Now at runtime in my project using breeze, when I try the same scenario: delete a Packing element like this:
var deletePackings = function (packingsObservable) {
// Input: packingsObservable: an observable filled with a list of packings to delete
// Output: none
for (var i = 0; i < packingsObservable().length; i++) {
packingsObservable()[i].entityAspect.setDeleted();
};
return manager.saveChanges();
};
I got the error below:
The UPDATE statement conflicted with the FOREIGN KEY constraint \"FK_dbo.Isotopes_dbo.Packings_PackingId\". The conflict occurred in database \"TRANSPORTBOEKDB\", table \"dbo.Packings\", column 'Id'.\r\nThe statement has been terminated."}
Now I read on another SO post that
Breeze does not yet support client side 'cascaded' deletes (we are considering this one), you will need to iterate over any client side orders that are already loaded and 'detach' them.
So is this the reason why I got the error in my application?
Do I have to iterate over any child entities that are already loaded and 'detach' them?
UPDATE
By manually detaching any child entities by code with breeze do the trick but this is painful:
var deletePackings = function (packingsObservable) {
// Input: packingsObservable: an observable filled with a list of packings to delete
// Output: none
// Remark: we loop from end to begin of the observable!
var entity;
// Since Breeze does not yet support client side 'cascaded' deletes (we are considering this one),
// you will need to iterate over any child entity that are already loaded and 'detach' them.
for (var i = packingsObservable().length - 1; i >= 0; i--) {
// Detach any child entities of type isotope
for (var j = packingsObservable()[i].isotopes().length - 1; j >= 0; j--) {
entity = packingsObservable()[i].isotopes()[j];
manager.detachEntity(entity);
}
// Detach any child entities of type chemicalForm
for (var j = packingsObservable()[i].chemicalForms().length - 1; j >= 0; j--) {
entity = packingsObservable()[i].chemicalForms()[j];
manager.detachEntity(entity);
}
// Detach any child entities of type physicalForm
for (var j = packingsObservable()[i].physicalForms().length - 1; j >= 0; j--) {
entity = packingsObservable()[i].physicalForms()[j];
manager.detachEntity(entity);
}
packingsObservable()[i].entityAspect.setDeleted();
};
return manager.saveChanges();
};
No better solution?
Do cascade delete in SQL if needed and forget in on client, just refresh data after.
Or you can add new IsActive column for agreggate entity, this is my prefered approach, I am scared of DB deletes:)
Support for cascade delete is a very reasonable request.
Please add a vote for client side cascade delete support here: Breeze User Voice. We take this venue very seriously in determining what features to add to the Breeze product.
The feature the breeze authors are considering is client-side cascade delete, but breeze also has no support for the server-side cascade delete. You have to do certain things in order to use a server-side cascade delete.
setDeleted nulls all referencing fkey values in the cache -- even if the fkey is non-nullable. If a referencing record has a non-nullable fkey constraint, then you need to prevent saving the child records since the UPDATE would fail. You're approach to evict the children from the cache before the save accomplishes this goal and allows you to call saveChanges() with no parameters. Alternatively, you can pass an array that contains the records you set deleted. You would then probably want to evict the children after the save. Or you can just stop using that entity manager.
Related
I have a many to many relationship in my code first.
public class Post
{
public int Id { get; set; }
public ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public int Id { get; set; }
public ICollection<Post> Posts { get; set; }
}
modelBuilder.Entity<Post>().HasMany(c => c.Tags).WithMany(a => a.Posts);
If i have a PostId and a TagId , How i can insert relationship with single query in entity framework (Without load Post or Tag and add relationship to that)
This is one of the drawbacks of the implicit junction table.
Still it's possible to do what you are asking by creating two "stub" entities, attach them to the context (this telling EF that they are existing), and adding one of them to the collection of the other:
using (var db = new YourDbContext())
{
var post = db.Posts.Attach(new Post { Id = postId });
var tag = db.Tags.Attach(new Tag { Id = tagId });
post.Tags = new List<Tag> { tag };
db.SaveChanges();
}
Due to the hack-ish nature of above technique, make sure to use it only with short lived contexts specifically allocated for the operation.
If I understood your question correctly, you want to ignore the insertion of navigation property. You can change state of the collection property as 'UnChanged' to avoid insertion of the property.
It will looks like;
_context.Posts.Add(post);
_context.Entry(post.Tags).State = EntityState.Unchanged;
_context.SaveChanges();
Useing Entity framework I want to include an only the first level of children objects and not the children of child
I have these two classes:
public class BusinessesTBL
{
public string ID { get; set; }
public string FirstName { get; set; }
public string lastName { get; set; }
public ICollection<OffersTBL> OffersTBLs { get; set; }
}
public class OffersTBL
{
public int ID { get; set; }
public string Name { get; set; }
public int CatId { get; set; }
public string BusinessesTBLID { get; set; }
public virtual BusinessesTBL BusinessesTBLs { get; set; }
}
when I try to bring all offers according to CatId field, I need to return the BusinessesTBLs also, but the method also return offers again per each BusinessesTBL obj , My code is :
public IQueryable<OffersTBL> GetOffersTBLsCat(int id)
{
db.OffersTBLs.Include(s => s.BusinessesTBLs);
}
You can see the wrong result on :
http://mycustom.azurewebsites.net/api/OffersApi/GetOffersTBLsCat/4
As you can see it return all offers under each Business object while business object under each offer, And I want only to return offers with its Business object without offer under Business obj.
Could anyone help please?
I now see that a big part of the original answer is nonsense.
Sure enough, the reason for the endless loop is relationship fixup. But you can't stop EF from doing that. Even when using AsNoTracking, EF performs relationship fixup in the objects that are materialized in one query. Thus, your query with Include will result in fully populated navigation properties OffersTBLs and BusinessesTBLs.
The message is simple: if you don't want these reference loops in your results, you have to project to a view model or DTO class, as in one of the other answers. An alternative, less attractive in my opinion, when serialization is in play, is to configure the serializer to ignore reference loops. Yet another less attractive alternative is to get the objects separately with AsNoTracking and selectively populate navigation properties yourself.
Original answer:
This happens because Entity Framework performs relationship fixup, which is the process that auto-populates navigation properties when the objects that belong there are present in the context. So with a circular references you could drill down navigation properties endlessly even when lazy loading is disabled. The Json serializer does exactly that (but apparently it's instructed to deal with circular references, so it isn't trapped in an endless loop).
The trick is to prevent relationship fixup from ever happing. Relationship fixup relies on the context's ChangeTracker, which caches objects to track their changes and associations. But if there's nothing to be tracked, there's nothing to fixup. You can stop tracking by calling AsNoTracking():
db.OffersTBLs.Include(s => s.BusinessesTBLs)
.AsNoTracking()
If besides that you also disable lazy loading on the context (by setting contextConfiguration.LazyLoadingEnabled = false) you will see that only OffersTBL.BusinessesTBLs are populated in the Json string and that BusinessesTBL.OffersTBLs are empty arrays.
A bonus is that AsNoTracking() increases performance, because the change tracker isn't busy tracking all objects EF materializes. In fact, you should always use it in a disconnected setting.
You have deactivated lazy loading on OffersTBLs making it non-virtual. What if you activate lazy loading? like this:
public class BusinessesTBL
{
public string ID { get; set; }
public string FirstName { get; set; }
public string lastName { get; set; }
//put a virtual here
public virtual ICollection<OffersTBL> OffersTBLs { get; set; }
}
Then, be sure to not call/include OffersTBLs when serializing. If the OffersTBLs are still returning, it is because you are fetching them somewhere in your code. If this is happening, edit your question and paste all the code, including the serializing logic.
Since OffersTBL has an association to BusinessesTBL and BusinessesTBL to OffersTBL you can loop infinitly throw the Entities like OffersTBL.BusinessesTBL.OffersTBL.BusinessesTBL and so on.
To control the nested depth of the Entities i'm usually using helperclasses with the needed properties in them.
For BusinessesTBL
public class BusinessesTBLHelper
{
private BusinessesTBLHelper(BusinessesTBL o){
ID = o.ID;
FirstName = o.FirstName;
lastName = o.LastName;
OffersTBLids = new List<int>();
foreach(OffersTBL offersTbl in o.OffersTBLs){
OffersTBLids.Add(offersTbl.ID);
}
}
public string ID { get; set; }
public string FirstName { get; set; }
public string lastName { get; set; }
public IEnumerable<int> OffersTBLids { get; set; } //no references anymore
}
And same for your OffersTBL Entity.
public class OffersTBLHelper
{
private OffersTBLHelper(OffersTBL o){
ID = o.ID;
Name = o.Name;
CatId = o.CatId;
BusinessesTBLID = o.BusinessesTBLID;
BusinessesTBLs = new BusinessesTBLHelper(o.BusinessesTBLs);
}
public string ID { get; set; }
public string Name{ get; set; }
public intCatId{ get; set; }
public string BusinessesTBLID { get; set; }
public BusinessesTBLHelper BusinessesTBLs { get; set; }
}
On quering database you can directly create the new helperobjects from queryresult:
public IEnumerable<OffersTBLHelper> GetOffersTBLsCat(int id)
{
return db.OffersTBLs.where(s => s.CatId == id).Select(x=> new OffersTBLHelper(x)).ToList();
}
Now you have all the OffersTBL with BusinessesTBLs under. The loop stops here because the BusinessesTBLs have no OffersTBL under it. However, it only has them Ids in a List for further referencing and identifying.
Assuming that the object isnt null and just empty:
public IQueryable<OffersTBL> GetOffersTBLsCat(int id)
{
db.OffersTBLs.Include(s => s.BusinessesTBLs).Where(x => !x.BusinessesTBLs.OffersTBLs.Any());
}
Edit: Filter before the include:
public IQueryable<OffersTBL> GetOffersTBLsCat(int id)
{
db.OffersTBLs.Where(x => !x.BusinessesTBLs.OffersTBLs.Any())
.Include(s => s.BusinessesTBLs);
}
I use breezejs in my Durandal web application.
Here is my code to get my invoice & lines behind it:
var getInvoiceById = function (invoiceId, invoiceObservable, forceRemote) {
// Input: invoiceId: the id of the invoice to retrieve
// Input: forceRemote: boolean to force the fetch from server
// Output: invoiceObservable: an observable filled with the invoice
if (forceRemote)
queryCacheInvoice = {};
var query = entityQuery.from('Invoices')
.where('id', '==', invoiceId)
.expand("Client, Client.Contacts, Lines")
.orderBy('Lines.Number');
var isInCache = queryCacheInvoice[invoiceId];
if (isInCache && !forceRemote) {
query = query.using(breeze.FetchStrategy.FromLocalCache);
} else {
queryCacheInvoice[invoiceId] = true;
query = query.using(breeze.FetchStrategy.FromServer);
}
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
invoiceObservable(data.results[0]);
}
};
And here is the models for Invoice:
public class Invoice
{
[Key]
public int Id { get; set; }
public string Number { get; set; }
public DateTime? Date { get; set; }
public int? ClientId { get; set; }
public string Comment { get; set; }
public double? TotalExclVAT { get; set; }
public double? TotalInclVAT { get; set; }
public double? TotalVAT { get; set; }
public bool? WithoutVAT { get; set; }
public virtual List<InvoiceLine> Lines { get; set; }
public virtual Client Client { get; set; }
}
Please notice that for each invoice I have many invoice lines:
public virtual List<InvoiceLine> Lines { get; set; }
And here is the models for InvoiceLine:
public class InvoiceLine
{
[Key]
public int Id { get; set; }
[Required]
public int Number { get; set; }
[Required]
public string Description { get; set; }
public int InvoiceId { get; set; }
public Invoice Invoice { get; set; }
}
The problem: when I execute this breeze query I got the error below:
Error retreiving data. unable to locate property: Lines on type: Invoice
The problem is around the orderBy clause. I have a 1-to-many relationship between the Invoice and the InvoiceLine so it seems I cannot perform an order by in this case.
My question: how to proceed to be able to sort my lines of invoice by number?
Thanks.
Short answer: You can't. This is a limitation of Entity Framework, not Breeze.
You cannot filter, select, or order the related entities that you include with "expand" in an EF LINQ query.
You will probably manage the sort order of related entities on the client, e.g., the display of your order line items.
Note also that the collection of entities returned by a Breeze navigation path is unordered. I wasn't sure what happens if you tried to sort a Breeze entity navigation collection (e.g., Order.LineItems). I was afraid that would cause Breeze to think that you had made changes to the entities ... because a sort would seem to remove-and-add entities to the collection as it sorted. Your EntityManager would think there were changes pending when, in fact, nothing of substance has changed.
I tried an experiment and it all seems to work fine. I did something like this with Northwind:
fetched the Orders of a Customer ("Let's Stop N Shop")
checked the sequence of cust.Orders(); they have unordered OrderIDs: [10719, 10735, 10884, 10579]
executed a line like this: cust.Orders().sort(function(left, right){return left.OrderID() < right.OrderID()? -1 : 1})
checked the sequence of cust.Orders() again; this time they are sorted: [10579, 10719, 10735, 10884]
checked the customer's EntityManager.hasChanges() ... still false (no changes).
I confess that I am happily surprised. I need to write a proper test to ensure that this works reliably. And I have to make sure that the Knockout binding to the navigation property displays them in the sorted order. But I'm encouraged so far.
Important Notes:
Breeze won't keep the list sorted. You'll have to do that if you add new orders or if Breeze adds new orders to the collection as a result of subsequent queries.
Your sort affects every view that is bound to this navigation property. If you want each view to have its own sort of the entities in that collection, you'll have to maintain separate, view-specific collections that shadow the navigation property collection.
If I am wrong about all of this, you'll have to manage a shadow collection of the related entities, sorted as you wish, for each ViewModel.
Update 2 June
I suspected that we would have to let KO know about the array change after sort by calling valueHasMutated. I took 15 minutes for an experiment. And it seems to work fine.
I created a new KO Todo app from the ASP.NET SPA template (there's currently a phantom complaint about a missing jQuery.UI library which is totally unnecessary anyway).
I added a "sort link" to the index.cshtml just above the delete-TodoList link:
Sort
Then I implemented it in viewModel.js:
var sortAscending = true;
var viewmodel = {
...
sortList: sortList,
...
};
...
function sortList(list) {
list.todos().sort(function(left, right) {
return (sortAscending ? 1 : -1) *
(left.title().toLowerCase() < right.title().toLowerCase() ? -1 : 1);
});
sortAscending = !sortAscending; // reverse sort direction
list.todos.valueHasMutated(); // let KO know that we've sorted
}
Works like a charm. Sorting, adding, deleting Todos at will. The app is saving when expected as I add and delete ... but not during save.
Give valueHasMutated a try.
I have created my application by using code-first approach in ASP.NET MVC 4.
I have three entities. Namely, "Company", "Service" and "ServiceFeature":
public class Company
{
public int CompanyID { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Description { get; set; }
public virtual Service SuppliedService { get; set; }
}
public class Service
{
public int ServiceID { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<ServiceFeature> ServiceFeatures { get; set; }
}
public class ServiceFeature
{
public int ServiceFeatureID { get; set; }
[Required]
public string Name { get; set; }
}
I have a search form, it consists of checkboxes for all ServiceFeatures. User will select checkboxes and get the results of Companies that are providing the Services with selected ServiceFeatures.
I get the Company list with my service as below but I'm stuck at how to include the selected ServiceFeatures in a where clause (dynamic LINQ with a for loop?)
var searchResults = _companyService.GetCompanies();
Assuming that you have a collection containing the IDs of the selected features, called requested, and assuming you want the companies supplying a service which contains ALL the selected features, you could for example do this:
var q = from c in searchResults
let sf = c.SuppliedService.ServiceFeatures
.Select(f => f.ServiceFeatureID)
.Intersect(requested)
where sf.Count() == requested.Count()
select c;
In similar cases I prefer an approach that's a bit more elaborate than a linq query with Intersect. Intersect can produce horrible queries with deep nesting, because the list with Id values to intersect over is built by SELECT and UNION commands for each Id in the list. This is not a problem when the number of Id's is low (true in your case, I assume), but with larger numbers it may throw a SQL exception.
So this is what I'd prefer:
var q = context.Companies.AsQueryable();
foreach(int i in featureIds)
{
int j = i; // prevent modified closure.
q = q.Where(c => c.SuppliedService.ServiceFeatures.Any(f => f.Id == j));
}
var result = q.ToList();
It builds a query with a number of WHERE EXISTS clauses. This can be very efficient because each EXISTS will seek (not scan) the primary index of ServiceFeature. Besides that, an INTERSECT is an implicit DISTINCT.
Well, just wanted to point this out. As said, with low numbers of records you won't notice any difference. So take whatever suits you best.
I have a simple Parent/Child table and I want to populate with one SQL statement using Entity Framework. Something like this:
public class Parent
{
public long ParentId { get; set; }
public virtual List<Child> Children { get; set; }
}
public class Child
{
public long ChildId { get; set; }
public long ParentId { get; set; }
public DateTime DateCreated { get; set; }
}
public Parent GetParent (long parentId)
{
IQueryable<Parent> query =
(from a in db.Parents.Include("Children")
where a.ParentId == parentId
select a);
return query.FirstOrDefault();
}
This seems to work in fetching the Parent and all Children in one SQL Statement. What I want to do now is order the children list by the DateCreated field, but I can't figure out how to do that. I've seen posts about using ThenBy() but don't see how to inject in the above Linq. Is there a better way I should be formulating the Linq?
-shnar
Unfortunately this is not supported. You cannot order included records by Linq-to-entities. Only parent records can be ordered. But you can still do something like:
var parent = GetParent(id);
var orderedChildren = parent.Children.OrderBy(c => c.DateCreated);