EF CF Sub Navigation with intersect table not working - entity-framework

I inherited a EF CF project and my knowledge of EF is inadequate. I am almost there. I have been reading and experimenting and I just can't figure out what I am doing wrong.
I have read numerous articles, tried reverse navigation, experimented and discovered strange new errors but no luck so far.
This is my setup. Of course the models have many more properties like Name that I left out for brevity.
public class VendorModel
{
[key]
public int Id { get; set; }
[ForeignKey("VendorId")]
public virtual List<VendorPersonnelModel> VendorPersonnel { get; set; }
}
//This model represents an intersect table in the DB
public class VendorPersonnelModel
{
[Key]
public int Id { get; set; }
public int VendorId { get; set; } //This is a FK to Vendor table defined in DB
public int PersonnelId { get; set; } //This is a FK to Personnel table defined in DB
}
//This model definition is here but not used till the second half of the question
public class PersonnelModel
{
[key]
public int Id { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
}
This is how I am navigating in the controller:
var models = await _db.Vendors
.Include(o => o.Addresses)
.Include(o => o.VendorPersonnel)
.ToListAsync();
This is db context definition:
var ent = modelBuilder.Entity<VendorPersonnelModel>();
ent.HasKey(obj => obj.Id);
ent.ToTable("VendorPersonnel");
//Navigation properties...do I put something here like this???
//ent.HasMany().HasForeignKey(y => y.PersonnelId);
This produces a json return like this:
[{
"name": "Vendor ABC",
"vendorPersonnel": [
{
"id": 1,
"vendorId": 1001001,
"personnelId": 1231
},
{
"id": 2,
"vendorId": 1001001,
"personnelId": 1776
}
]
}]
This is very close to what I want. Now I want to resolve the other half of the intersect table...the personnel. I do NOT need the intersect details I was just trying to step my way towards the solution.
I want the json to look something like this:
[{
"name": "Vendor ABC",
"personnelDetails": [
{
"id": 1,
"Name": "Jane",
"Phone": "333-123-4567"
},
{
"id": 2,
"Name": "Joe",
"Phone": "675-943-6732"
}
]
}]
This is where I start getting all sorts of strange errors. I CAN make it work by doing two DIFFERENT queries and then mashing them together in a new object and return the new object but that just seems to me to be poor coding.
I'm sure there is a way to do this with EF I just don't know what to look for / read up on.

I have now been able to read up and answer my question. This article helped me with what was missing:
https://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx
This mapping will create a self referencing "appearance" that will freak a json formatter out.
Simply add:
HttpConfiguration config = GlobalConfiguration.Configuration;
config.Formatters.JsonFormatter
.SerializerSettings
.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
Source: Entity framework self referencing loop detected

Related

EntityFramework Many-to-Many serilization cycle

I have a code first Blazor WebAssembly application in which I have a Many-to-Many relationship.
public class A
{
public A()
{
this.Bs = new HashSet<B>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<B> Bs { get; set; }
}
public class B
{
public B()
{
this.As= new HashSet<A>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<A> As { get; set; }
}
In my client, I call the Get method of the server AController. I would like to have in each A object, the Bs ICollection.
If the Get method is like this, the Bs collection is null :
[HttpGet]
public IEnumerable<A> Get()
{
return _context.A.ToList();
}
If it is like this, to inlude Bs, I have an exception ("System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles.")
[HttpGet]
public IEnumerable<A> Get()
{
return _context.A.Include(a => a.Bs).ToList();
}
So in my Startup.cs (on the server) I had the following in the ConfigureServices method
services.AddControllers().AddJsonOptions(o =>
o.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve);
So now, the serialization works, but the deserialization fails because the JSON is different, not only a List of A.
In the client, I call the get method of the AController like this :
var response = await _httpClient.GetAsync("my_api_adresse");
return await response.Content.ReadFromJsonAsync<List<A>>();
But du to the ReferenceHandler.Preserve the JSON is like this, so the desirialization can't work and raises an exception :
{
"$id": "1",
"$values": [
{
"$id": "2",
"id": 4,
"name": "nameA3",
"Bs": {
"$id": "3",
"$values": [
{
"$id": "4",
"id": 1,
"Name": "NameB1",
"As": {
"$id": "5",
"$values": [
{
"$ref": "2"
}
]
}
}
]
}
}
]
}
What could I do to be able to include Bs collection into A objects and be able to serialize and deserialize the response without any trouble ?
Try using DTO objects (data transfer objects).
There are many ways. One is:
Create two new classes that are similar to class A and class B, but this one without a list of A objects. Instead, use a list of A Ids. That would solve your problem.
Comment gere if you need code sample

Attaching to existing object in a many to many relation with Entitiy Framework Code First

I'm working on a Rest API using Web API2 and EF6 Code first starting from the guide on http://www.asp.net/web-api/overview/data/using-web-api-with-entity-framework/part-4
I'm basically doing the same thing having a many to many relationship and when i am creating an object, lets call it A, i include an array of B objects as the Bs variable in the post. They all get created as expected on the initial post, however when i add a second Object A which should link to one or more of the same B's as the first object A it instead of matching to the existing B's tries to create new B's but since there is a constraint on the name of the B this wont work. How do i ensure that it does not try to create a new object B every-time and instead link to an existing Object B if there is one?.
Here is the example in more detail.
I have two Models, lets call them A and B. They have a many to many relation
public class A
{
public int Id { get; set; }
[Required, StringLength(100), Index("IDX_Name", 2, IsUnique = true)]
public string Name { get; set; }
[StringLength(300)]
public string Description { get; set; }
public ICollection<B> Bs{ get; set; }
}
Model B
public class B
{
public int Id { get; set; }
[Required, StringLength(100), Index("IDX_Name", 2, IsUnique = true)]
public string Name { get; set; }
public ICollection<B> As{ get; set; }
}
I'm not including the auto generated context.
and in the auto generated Controller scaffolding for the Web API POST method for Model A it looks like this
[ResponseType(typeof(A))]
public async Task<IHttpActionResult> PostGame(A a)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.As.Add(a);
await db.SaveChangesAsync();
return CreatedAtRoute("DefaultApi", new { id = a.Id }, a);
}
All tables are created just fine and if i do the first post creating my first object A with the following json:
{
"Name": "FirstA",
"Description": "FirstADesc",
"Bs" : [{"Name":"FirstB"}]
}
It works and both FirstA and FirstB is created.
If i then post a SecondA which is also linked to the FirstB obect
{
"Name": "SecondA",
"Description": "SecondADesc",
"Bs" : [{"Name":"SecondB"},{"Name":"FirstB"}]
}
It will instead of finding the FirstB try to create it again. Which it due to the constraint.
My first guess was that i should use the ID:s instead in the second post. like:
{
"Name": "SecondA",
"Description": "SecondADesc",
"Bs" : [{"Id":"1"},{"Name":"FirstB"}]
}
but this does not work either.
Is the only way of achieving this to replace the scaffolding code from the controller and check each object in Bs manually if it exist already?.
Basically its a "Tags to a post problem"...
I've looked into the Attached vs Detached Data subject and read articles on the matter without finding an answer that i can understand whether this can be done automatically with some proper annotations or if it has to be done "manually" in the controller.
Thanks in Advance!
Not much response on this one,
Ended up looping and manually checking for an existing entry like below.
[ResponseType(typeof(A))]
public async Task<IHttpActionResult> PostGame(A a)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
foreach (var asd in a.Bs.ToList())
{
var t = db.Bs.FirstOrDefault(a => a.Name == asd.Name);
if (t != null)
{
a.Bs.Remove(asd);
a.Bs.Add(t);
}
}
db.As.Add(a);
await db.SaveChangesAsync();
return CreatedAtRoute("DefaultApi", new { id = a.Id }, a);
}
Cant help to feel that there has to be a better way then this though.

EDIT:EF6 Navigation Property ignored by Breeze JS; Foreign Keys Defined

I have three entities: Districts, Sites, and Repayments (Actually there are about a dozen, but these are the important ones). THe Relavent Code is:
public class Site
{
[Key, Column(Order = 0)]
public int DistrictId { get; set; }
[Key, Column(Order = 1)]
public int SiteId { get; set; }
public string Name { get; set; }
public virtual District District { get; set; }
}
public class District
{
public int DistrictId { get; set; }
public string Name { get; set; }
public virtual ICollection<Repayment> Repayments { get; set; }
}
public class Repayment
{
[Key, Column(Order = 0)]
public int DistrictId { get; set; }
[Key, Column(Order = 1)]
public int RepaymentId { get; set; }
public string Name { get; set; }
[InverseProperty("Repayments")]
[ForeignKey("DistrictId")]
public virtual District District { get; set; }
}
Then, in the FluentAPI I have:
modelBuilder.Entity<Repayment>().HasRequired(r => r.District)
.WithMany(d => d.Repayments)
.HasForeignKey(r => new { r.DistrictId});
The model performs correctly on the C# side, making repayments a collection in a District when I use the appropriate Include clause. Breeze, however, cannot find the repayments collection under the district (the repayments are in cache under a couple of other entities). I have tried every combination of Fluent API and Data Annotation possible (using only data annotation, using partial data annotation with and without the fluent api, etc) and there is no difference.
An interesting aside, the client side code works perfectly (and populates the repayments under districts) when I run it against a 1.5.1 version of Context Provider and 2.1.5.1 version of BreezeWebApi (the version that is not working is 1.5.2 and 2.1.5.2). However, I'm porting from a less complex model to a more complex model, so the backend is not exactly the same (those these classes are byte for byte the same).
I'd be grateful for any ideas.
EDIT:
I have just installed the more recent versions (2.1.5.2 and 1.5.2) on the working site, and it caused no problems. So the version change doesn't seem to be the culprit. Are there any other things I should be checking?
TIA
EDIT: Even more information
There are, within a district, 10 total one-to-many navigation propertys, of which Repayments is only one. 8 of them are being populated correctly (and are defined in exactly the same way) and two (including the Repayments property) are not. On the server side, all 10 properties are recognized and populated, while on the client side Breeze is only "wiring up" 8 of the properties to District (all the related entities are in cache and wired to at least one other entity, just not the District entity). Very strange. Help?
EDIT: Curiouser and curiouser:
The breeze metadata seems to have the appropriate navigation property definitiions. Below is the NavProp definitions for Repayments (not working) and SeasonClients (working) in the District Entity:
{
"name": "Repayments",
"relationship": "Self.Repayment_District",
"fromRole": "Repayment_District_Target",
"toRole": "Repayment_District_Source"
},
{
"name": "SeasonClients",
"relationship": "Self.SeasonClient_District",
"fromRole": "SeasonClient_District_Target",
"toRole": "SeasonClient_District_Source"
},
And here is how the Nav Property for District is defined in both entities:
REPAYMENT Nav Properties:
{
"name": "District",
"relationship": "Self.Repayment_District",
"fromRole": "Repayment_District_Source",
"toRole": "Repayment_District_Target"
},
SEASON CLIENT Nav Properties:
{
"name": "District",
"relationship": "Self.SeasonClient_District",
"fromRole": "SeasonClient_District_Source",
"toRole": "SeasonClient_District_Target"
},
And here is the SeasonClient_District relation definition:
{
"name": "SeasonClient_District",
"end": [{
"role": "SeasonClient_District_Source",
"type": "Edm.Self.SeasonClient",
"multiplicity": "*"
},
{
"role": "SeasonClient_District_Target",
"type": "Edm.Self.District",
"multiplicity": "1",
"onDelete": {
"action": "Cascade"
}
}],
"referentialConstraint": {
"principal": {
"role": "SeasonClient_District_Target",
"propertyRef": {
"name": "DistrictId"
}
},
"dependent": {
"role": "SeasonClient_District_Source",
"propertyRef": {
"name": "DistrictId"
}
}
}
},
Which is byte-for-byte the same (if you replace 'SeasonClient' with 'Repayment') as the Repayment_District relationship definition.
Any thoughts?
J had similar problem. In June 2014 (all the latest libs at that time) the following client side code worked:
return EntityQuery.from('FlowCharts')
.withParameters({ flowChartId: 1 })
.expand('Nodes.NodeFunction.NodeFunctionParameters, Connections')
.using(manager).execute()
.then(querySucceeded)
.catch(_queryFailed);
but not now (Feb 2015 - after updating to the latest libs).Changing related entities names to lowercase has helped (please read #Ward debugging tips):
return EntityQuery.from('flowCharts')
.withParameters({ flowChartId: 1 })
.expand('nodes.nodeFunction.nodeFunctionParameters, connections')
.using(manager).execute()
.then(querySucceeded)
.catch(_queryFailed);
Hope it helps.
Grzech

Web API OData $expand doesn't return complex types

Domain model:
public class Course
{
public int CourseId { get; set; }
public virtual ICollection<TeeSet> TeeSets { get; set; }
}
public class TeeSet
{
public int TeeSetId { get; set; }
public int CourseId { get; set; }
public CourseRating MensRating { get; set; }
}
The following query does not include the CourseRating complex type when Courses are expanded to include TeeSets.
GET /api/courses?$expand=TeeSets
public class CoursesController : ApiController
{
[Queryable]
public IQueryable<Course> Get()
{
return _uow.Courses.GetAll();
}
}
The JSON serialized result does not include the MensRating complex type (CourseRating):
[
{
"teeSets": [
{
"teeSetId": 1,
"courseId": 7
},
{
"teeSetId": 2,
"courseId": 7
}
],
"courseId": 7,
}
]
However, a quick test against the DbContext returns the CourseRating complex type on TeeSets like I would expect:
[TestMethod]
public void Get_Course_With_TeeSets()
{
using (CoursesContext ctx = new CoursesContext())
{
var courses = ctx.Courses.Where(x => x.CourseId == 7).Include(x => x.TeeSets).FirstOrDefault();
}
}
Entity Framework 6 and Web API 2 used.
You should expand MensRating as well like this, GET /api/courses?$expand=TeeSets/MensRating
When we build an implicit EDM model for supporting QueryableAttribute with ApiController, we treat every type as an entity type to get around the OData V3 limitation that complex types cannot refer to entity types. And, this means that you have to expand explicitly every non primitive type.
Add AutoExpand on MensRating Property can make this work.

Insertion order of multiple records in Entity Framework

I'm having trouble with EF reordering my inserts when I try and add an entity with multiple children all at once. I've got a 3 level structure with one-to-many relationships between each (Outer 1--* Item 1--* SubItem). If I try and insert a new Outer with Items and Subitems, the Items which contain SubItems end up being inserted first.
Sample Code (.NET 4.5, EF 5.0.0-rc):
public class Outer
{
public int OuterId { get; set; }
public virtual IList<Item> Items { get; set; }
}
public class Item
{
public int OuterId { get; set; }
[ForeignKey("OuterId")]
public virtual Outer Outer { get; set; }
public int ItemId { get; set; }
public int Number { get; set; }
public virtual IList<SubItem> SubItems { get; set; }
}
public class SubItem
{
public int SubItemId { get; set; }
[ForeignKey("ItemId")]
public virtual Item Item { get; set; }
public int ItemId { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Outer> Outers { get; set; }
public DbSet<Item> Items { get; set; }
public DbSet<SubItem> SubItems { get; set; }
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
MyContext context = new MyContext();
// Add an Outer object, with 3 Items, the middle one having a subitem
Outer outer1 = new Outer { Items = new List<Item>() };
context.Outers.Add(outer1);
outer1.Items.Add(new Item { Number = 1, SubItems = new List<SubItem>() });
outer1.Items.Add(new Item { Number = 2, SubItems = new List<SubItem>(new SubItem[] { new SubItem() }) });
outer1.Items.Add(new Item { Number = 3, SubItems = new List<SubItem>() });
context.SaveChanges();
// Print the order these have ended up in
foreach (Item item in context.Items)
{
Console.WriteLine("{0}\t{1}", item.ItemId, item.Number);
}
// Produces output:
// 1 2
// 2 1
// 3 3
}
}
I'm aware of this answer by Alex James which states that inserts may need to be reordered in order to satisfy relational constraints, but that is not the issue here. His answer also mentions that they can't track the order of items in order-preserving structures such as Lists.
What I'd like to know is how I can get these inserts to be ordered. While I can rely on sorting my inserted items by a field other than the PK, it's a lot more efficient if I can rely on the PK order. I don't really want to have to use multiple SaveChanges calls to accomplish this.
I'm using EF5 RC, but judging by the other unanswered questions around, this has been around for some time!
What I'd like to know is how I can get these inserts to be ordered.
You cannot. Order of database commands is EF's internal behavior. If you want to control the order of commands don't use tools which abstract you from low level database interactions - use SQL directly.
Edit based on comment:
Yes it is low level interaction because you are putting expectations on the order of SQL commands when working with abstraction you don't have under your control. At high level you are getting something different because you are using expectations which don't work with that abstraction. If you want to have control over order of SQL commands you must either force EF by saving items one by one (=> multiple SaveChanges and TransactionScope) or write SQL yourselves. Otherwise use separate column for ordering.
Btw. EF doesn't save the entity as you see it. It has its own change tracker holding references to all your attached instances. References are held in multiple Dictionary instances and dictionary doesn't preserve insertion order. If these collections are used for generating SQL commands (and I guess they are) no order can be guaranteed.
Tables in the database are sets. That means that the order is not guaranteed. I assume in your example that you want the results ordered by "Number". If that is what you want, what are you going to do if that number changes and it doesn't reflect the order in the database anymore?
If you really want to have the rows inserted in a specific order, multiple SaveChanges are your best bet.
The reason nobody wants to call SaveChanges multiple times is because this feels exactly how it is: a dirty hack.
Since a primary key is a technical concept, it shouldn't make any functional sense to order your results on this key anyway. You can order the results by a specific field and use a database index for this. You probably won't see the difference in speed.
Making the ordering explicit has other benefits as well:
it is easier to understand for people who have to maintain it. Otherwise that person has to know that ordering on primary key is important and gives the correct results, because in an other (completely) unrelated section of your application, it accidentally is the same order as the number field.
Another way of doing this, without database round trip after each entry added (heavily dependent on the application logic though) is via combination of entity state changes.
In my case - hierarchy of nodes - I had to persist root nodes first, then rest of the hierarchy in order to automatic path calculations to work.
So I had a root nodes, without parent ID provided and child nodes with parent ID provided.
EF Core randomly (or through complex and intelligent logic - as you prefer :) randomly scheduled nodes for insertion, breaking path calculation procedure.
So I went with overriding SaveChanges method of the context and inspecting entities from the set for which I need to maintain certain order of inserts - detaching any child nodes first, then saving changes, and attaching child nodes and saving changes again.
// select child nodes first - these entites should be added last
List<EntityEntry<NodePathEntity>> addedNonRoots = this.ChangeTracker.Entries<NodePathEntity>().Where(e => e.State == EntityState.Added && e.Entity.NodeParentId.HasValue == true).ToList();
// select root nodes second - these entities should be added first
List<EntityEntry<NodePathEntity>> addedRoots = this.ChangeTracker.Entries<NodePathEntity>().Where(e => e.State == EntityState.Added && e.Entity.NodeParentId.HasValue == false).ToList();
if (!Xkc.Utilities.IsCollectionEmptyOrNull(addedRoots))
{
if (!Xkc.Utilities.IsCollectionEmptyOrNull(addedNonRoots))
{
// detach child nodes, so they will be ignored on SaveChanges call
// no database inserts will be generated for them
addedNonRoots.ForEach(e => e.State = EntityState.Detached);
// run SaveChanges - since root nodes are still there,
// in ADDED state, inserts will be executed for these entities
int detachedRowCount = base.SaveChanges();
// re-attach child nodes to the context
addedNonRoots.ForEach(e => e.State = EntityState.Added);
// run SaveChanges second time, child nodes are saved
return base.SaveChanges() + detachedRowCount;
}
}
This approach does not let you preserve order of individual entities, but if you can categorize entities in those that must be inserted first, and those than can be inserted later - this hack may help.
I've found a way to do it. It just thought I'd let you know:
using (var dbContextTransaction = dbContext.Database.BeginTransaction())
{
dbContext.SomeTables1.Add(object1);
dbContext.SaveChanges();
dbContext.SomeTables1.Add(object2);
dbContext.SaveChanges();
dbContextTransaction.Commit();
}
multiple Add() before a save or AddRange() before a save does not preserve order. Also when you are reading the collection it is not guaranteed to return the results in the same order they were originally added. You need to add some property to your entities and when you query use OrderBy() to ensure they come back in the order you want.
It's not professional. But, I solved my problem with this method.
PublicMenu.cs file:
public class PublicMenu
{
public int Id { get; set; }
public int? Order { get; set; }
public int? ParentMenuId { get; set; }
[ForeignKey("ParentMenuId")]
public virtual PublicMenu Parent { get; set; }
public virtual ICollection<PublicMenu> Children { get; set; }
public string Controller { get; set; }
public string Action { get; set; }
public string PictureUrl { get; set; }
public bool Enabled { get; set; }
}
PublicMenus.json file
[
{
"Order": 1,
"Controller": "Home",
"Action": "Index",
"PictureUrl": "",
"Enabled": true
},
{
"Order": 2,
"Controller": "Home",
"Action": "Services",
"PictureUrl": "",
"Enabled": true
},
{
"Order": 3,
"Controller": "Home",
"Action": "Portfolio",
"PictureUrl": "",
"Enabled": true
},
{
"Order": 4,
"Controller": "Home",
"Action": "About",
"PictureUrl": "",
"Enabled": true
},
{
"Order": 5,
"Controller": "Home",
"Action": "Contact",
"PictureUrl": "",
"Enabled": true
}
]
DataContextSeed file
public class DataContextSeed
{
public static async Task SeedAsync(ICMSContext context, ILoggerFactory loggerFactory)
{
try
{
if (!context.PublicMenus.Any())
{
var publicMenusData = File.ReadAllText("../Infrastructure/Data/SeedData/PublicMenus.json");
var publicMenus = JsonSerializer.Deserialize<List<PublicMenu>>(publicMenusData);
foreach (var item in publicMenus)
{
context.PublicMenus.Add(item);
await context.SaveChangesAsync();
}
}
}
catch (Exception ex)
{
var logger = loggerFactory.CreateLogger<ICMSContextSeed>();
logger.LogError(ex.Message);
}
}
}
Crap!
The question is asked 9 years ago, and Microsoft still not doing anything with it.
In EF6, I found if I use
for(int i=0; i<somearray.Length; i++)
db.Table.Add(new Item {Name = somearray[i]});
It did in order, but if I use
foreach(var item in somearry)
db.Table.Add(new Item {Name = item})
then not in order.
When I used EF core 3.1, it is always not in order of the above two scenario.