I have a DbDataController which delivers a List of Equipment.
public IQueryable<BettrFit.Models.Equipment> GetEquipment() {
var q= DbContext.EquipmentSet.OrderBy(e => e.Name);
return q;
}
In my scaffolded view everything looks ok.
But the Equipment contains a HashSet member of EquipmentType. I want to show this type in my view and also be able to add data to the EquipmentType collection of Equipment (via a multiselect list).
But if I try to include the "EquipmentType" in my linq query it fails during serialisation.
public IQueryable<BettrFit.Models.Equipment> GetEquipment() {
var q= DbContext.EquipmentSet.Include("EquipmentType").OrderBy(e => e.Name);
return q;
}
"Object Graph for Type EquipmentType Contains Cycles and Cannot be Serialized if Reference Tracking is Disabled"
How can I switch on the "backtracking of references"?
Maybe the problem is that the EquipmentType is back-linking through a HashSet? But I do not .include("EquipmentType.Equipment") in my query. So that should be ok.
How is Upshot generating the model? I only find the EquipmentViewModel.js file but this does not contain any model members.
Here are my model classes:
public class Equipment
{
public Equipment()
{
this.Exercise = new HashSet<Exercise>();
this.EquipmentType = new HashSet<EquipmentType>();
this.UserDetails = new HashSet<UserDetails>();
}
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Picture { get; set; }
public string Link { get; set; }
public string Producer { get; set; }
public string Video { get; set; }
public virtual ICollection<EquipmentType> EquipmentType { get; set; }
public virtual ICollection<UserDetails> UserDetails { get; set; }
}
public class EquipmentType
{
public EquipmentType()
{
this.Equipment = new HashSet<Equipment>();
this.UserDetails = new HashSet<UserDetails>();
}
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Equipment> Equipment { get; set; }
public virtual ICollection<UserDetails> UserDetails { get; set; }
}
try decorating one of the navigation properties with [IgnoreDataMember]
[IgnoreDataMember]
public virtual ICollection<Equipment> Equipment { get; set; }
The model generated by upshot can be found on the page itself. In your Index view you will see the UpshotContext HTML helper being used (given that you are using the latest SPA version), in which the dataSource and model type are specified.
When the page is then rendered in the browser, this helper code is replaced with the actual model definition. To see that, view the source code of your page in the browser and search for a <script> tag that starts with upshot.dataSources = upshot.dataSources || {};
Check here for more info about how upshot generates the client side model.
As for the "backtracking of references", I don't know :)
I figured out - partially how to solve the circular reference problem.
I just iterated over my queried collection (with Include() ) and set the backreferences to the parent to NULL. That worked for the serialisation issue which otherwise already breaks on the server.
The only problem now is the update of a data entity - its failing because the arrays of the referenced entitycollection are static...
To solve the cyclic backreference, you can use the IgnoreDataMember attribute. Or you can set the back reference to NULL before returning the data from the DbDataController
I posted a working solution to your problem in a different question, but using Entity Framework Code First.
https://stackoverflow.com/a/10010695/1226140
Here I show how to generate your client-side model manually, allowing to you to map the data however you please
Related
I have a model that looks as follows:
public partial class RoutePartner
{
public Guid Id { get; set; }
[Required]
public Guid PartnerId { get; set; }
[Required]
public string RouteCompany { get; set; }
}
The partnerId comes from another TABLE that has an Id and Name. I'm trying to build my CRUD forms to load the list of partners and make them available to the form. That is the best way to do this in MVC Core? I see many .NET examples but none for CORE.
One last point is that partner drop down should be populated by a stored procedure.
Thanks
Dan
Your class doesn't have to be the same as your database. Why not have a class called Partner that has the ParnterID and PartnerName? In your RoutePartner class have a Partner property, instead of just the ID.
Public class SomeViewModel //Used by your view
{
public RouteParnter {get; set;}
public ParnterList List<SelectListItem> {get; set;}
public static List<SelectListItem> PopulateDropdown()
{
List<SelectListItem> ls = new List<SelectListItem>();
DataTable someTable = Database.LoadParnters(); // Database is your data layer
foreach (var row in someTable.Rows)
{
ls.Add(new SelectListItem() { Text = row["PartnerName"].ToString();, Value = row["PartnerID"].ToString(); });
}
return ls;
}
}
public partial class RoutePartner
{
public Guid Id { get; set; }
[Required]
public Partner SomePartner { get; set; }
[Required]
public string RouteCompany { get; set; }
public RouteParnter(Guid id)
{
DataRow returnedRow = Database.LoadRouteParnter(id);
Id = id;
// Assuming your DataRow returned has all the info you need.
RouteCompany = row["RouteCompany"];
SomePartner = new Partner();
SomePartner.PartnerId = row["PartnerID"]; // cast to guid, just example
SomePartner.PartnerName = row["PartnerName"].ToString();
}
}
public class Partner
{
public Guid PartnerId { get; set; }
public string PartnerName { get; set; }
}
I would loosely couple the data layer. You could use ADO.NET or any other way to communicate with the database. I usually do this by creating a parameterized constructor (say taking in an ID to load), to call the data layer. The data layer returns a DataTable, DataRow, DataSet, or a custom object. In the constructor, you populate the values of the RouteParnter object. Likewise you could have a List in the ViewModel.
I wanted to provide a quick update to how I solved this issue. I ended up creating a PartnerContext for EF. Once created, in controllers that need access to the list, I placed PartnerContext.ToList() into ViewBag and then used ASP helpers to load the list.
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 am developing a blog application in ASP.NET MVC and I have doubt whether my solution of updating some related classes is fully correct.
I have a class representing texts:
public partial class Text
{
public long ID { get; set; }
public string Title { get; set; }
...
public virtual ICollection<Tag> Tags { get; set; }
}
and tags:
public partial class Tag
{
public long ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Text> Texts { get; set; }
}
When I'm editing a texts, the TextEditorViewModel is being passed:
public class TextEditorViewModel
{
public long ID { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public IEnumerable<TagEditorViewModel> Tags { get; set; }
}
TagEditorViewModel is:
public class TagEditorViewModel
{
public long ID { get; set; }
public string Name { get; set; }
public bool IsInText { get; set; }
}
In the view I'm checking appropiate tags (this is reflected by IsInText property) and this works fine and correct data is being passed back to the controller.
Here is my code for the repository on which controller is working:
Text OriginalText = Mapper.Map<Text>(Text);
IEnumerable<long> tags = OriginalText.Tags.Select(tag => tag.ID);
ICollection<Tag> tagobj = context.Tags.Where(tag => tags.Contains(tag.ID)).ToList();
OriginalText.Tags = tagobj;
Text is object of TextEditorViewModel which is passed back on post. I'm using mapper to bind only primitive types and I'm rewriting all collection and reference types (as seen above), only tags marked as not in the text are filtered out. Is there any simpler way of managing the tags instead of quering the database and rewriting them?
You can attach the tags manually to the context instead of reloading them from the database because you have the ID of the tags which is sufficient to create/update a relationship:
Text OriginalText = Mapper.Map<Text>(Text);
foreach (var tag in OriginalText.Tags)
context.Tags.Attach(tag);
context.Texts.Add(OriginalText);
context.SaveChanges();
(I've taken the last two lines from your comment.)
Even though the properties in the different tags don't have the values from the database (except the ID) it will work because EF only needs the correct tag ID to create the relationship.
Perhaps I'm missing something here, but it seems that anything in the object model tree 3 or more levels down, is ignored when using TryUpdateModel.
For example (simplified):
public virtual ActionResult SomeAction(int id, FormCollection form)
{
IValueProvider vpFrom = form.ToValueProvider();
/*
At this stage, vpForm contains:
1)PropertyA
2) PropertyB.SubPropertyA
3) PropertyB.SubPropertyB.SubSubPropertyA
*/
TryUpdateModel(someObjectModel, null, null, null, vpFrom);
//The first two properties are applied, number (3) seems to be ignored
Am I missing something here? If this is just the way it is, has anyone come up with a workaround?
A quick project created with the following model.
public class TestModel {
public TestModelA A { get; set; }
public string Name { get; set; }
}
public class TestModelA {
public TestModelB B { get; set; }
public string Name { get; set; }
}
public class TestModelB {
public TestModelC C { get; set; }
public string Name { get; set; }
}
public class TestModelC {
public TestModelD D { get; set; }
public string Name { get; set; }
}
public class TestModelD {
public TestModelE E { get; set; }
public string Name { get; set; }
}
public class TestModelE {
public string Name { get; set; }
}
Here's my edit - which is essentially the same as yours
[HttpPost]
public ActionResult Edit(FormCollection form) {
IValueProvider vpFrom = form.ToValueProvider();
Models.TestModel t = new Models.TestModel();
TryUpdateModel(t, null, null, null, vpFrom);
return View(t);
}
This all works exactly as expected with all the models created properly. The only problem that I can see happening is that you possibly aren't passing the same property names back from the form. (by not using <%: Html.TextBoxFor(model => model.A.B.C.CName)%> for example)
The models require parameterless constructors. But I'm sure you would have gotten an error about that - unless you're consuming the error.
So without more information about your project it will be hard to help as a basic setup produces expected results.
I believe the problem is in one of your model classes. Check, please, if PropertyB.SubPropertyB.SubSubPropertyA is really a property but not a field. A property should have get and set accessors.
Here's my checklist:
Make sure you're getting the value back in the form request. Request["A.B.C.Name"] and etc.
All the required fields are on the form.
I had deleteOnNull issue with Linq to SQL: How to set DeleteOnNull from designer for future ref if you're using L2SQL.
I am using EFExtensions with a typed view in mvc and am getting
The model item passed into the dictionary is of type 'Microsoft.Data.Extensions.Materializer`1+d__0[MvcCms.Web.Models.User]' but this dictionary requires a model item of type 'MvcCms.Web.Models.ViewData.SiteAdminModel'.
'MvcCms.Web.Models.ViewData.SiteAdminModel' contains a definition of User, here is the code in SiteAdminModel
public class SiteAdminModel
{
public StateProvince SelectedState { get; set; }
public IEnumerable<StateProvince> States { get; set; }
public IEnumerable<Organization> Organizations { get; set; }
public IEnumerable<User> Users { get; set; }
}
What would be the best way to fix this. Possibly using a codebehind to cast the Materialized version or doing that in the controller?
The problem was occuring because I was passing the model through in the renderpartial and it didn't need to be passed there because the control had access without it.