Unique index with fluent api - ef-code-first

I got a table Person, Order and PersonOrder.
In the table PersonOrder i have a column PersonId and OrderId.
How can I create a unique index between these columns with Fluent API?
This was my try:
modelBuilder.Entity<PersonOrder>()
.HasKey(l => new { l.PersonId , l.OrderId});
[Table("PersonOrder")]
public class PersonOrder
{
public int PersonId { get; set; }
public int OrderId{ get; set; }
public virtual Person Person { get; set; }
public virtual Order Order { get; set; }
}

I had to think for a while to get what you probably mean. I think you mean that there are non-unique indexes on PersonOrder.PersonId and PersonOrder.OrderId.
But there is a unique index on the primary key PersonOrder.PersonId + PersonOrder.OrderId. And that's the only thing that should be unique. The index on the individual fields can never be unique. That would mean that the association is in fact not many-to-many, but one-to-many.
Or is that what you're after: to have a 1 (Person) to many (Orders) association? (With a unique PersonOrder.OrderId). In that case you might as well model the association as a regular 1 to many: Person.Orders and Order.Person (singular).

what's wrong with that? - that's the way for a many-to-many composite index...
I'll just post the full example how it's usually done / mapped for many to many with all columns (and typical navigation properties that you probably have already):
modelBuilder.Entity<PersonOrder>()
.HasKey(x => new { x.PersonId, x.OrderId });
modelBuilder.Entity<PersonOrder>()
.HasRequired(x => x.Person)
.WithMany(x => x.PersonOrders)
.HasForeignKey(x => x.PersonId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<PersonOrder>()
.HasRequired(x => x.Order)
.WithMany(x => x.PersonOrders)
.HasForeignKey(x => x.OrderId)
.WillCascadeOnDelete(false);

Related

Entity Framework with ASP.NET one-to-many relation causes NullReference error

In my database I have two entities: DbStatus and DbTask
public class DbStatus
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<DbTask> Tasks { get; set; }
}
public class DbTask
{
public int Id { get; set; }
public string Title { get; set; }
public bool Done { get; set; }
public int StatusId { get; set; }
public virtual DbStatus Status { get; set; }
}
In the OnModelCreating method, I establish the relation with the following code:
modelBuilder.Entity<DbStatus>()
.HasMany(s => s.Tasks)
.WithOne(t => t.Status)
.HasForeignKey(t => t.StatusId);
I also add some sample data in this method, setting the StatusId of newly created DbTasks.
Problem is, when I try to access the Status name of the DbTask using
task.Status.Name
I get a NullReferenceException.
Can anyone help me how to set up the relation properly?
IMPORTANT
For anyone reading this, the quickest solution (and the one fulfilling task-specific criterias) for this was provided Rob. However, you should read and implement the solution provided by Steve Py, for the reasons they also describe in their answer!
When getting your list of DbTasks from the database, you need to tell it to include the child Status objects.
Try something like this:
var tasks = dbContext.DbTasks
.Include(t => t.Status)
.ToList();
Setting a FK on an entity does not automatically cause that related entity to be loaded. When working with navigation properties I recommend avoiding declaring FK fields in entities and using shadow properties to avoid issues like this.
To update a status on a DbTask:
public ActionResult MarkTaskComplete(int taskId)
{
var completeStatus = _context.Statuses.Single(x => x.StatusId = Statuses.Complete);
// TODO: Validation that user can update task etc.
var task = _context.Tasks
.Include(x => x.Status)
.Single(x => x.TaskId == taskId);
task.Status = completeStatus;
_context.SaveChanges();
return Json(new { success = true; status = task.Status.Name } );
}
The issue with FK fields is that the behaviour can differ depending on whether you use the navigation property or the FK, and whether the navigation property is eager loaded or not. From the perspective of the Task, there are two sources of truth for the current Status, some code might check task.StatusId while others use task.Status.StatusId. These values could differ depending on one being updated without the other.
While this can mean a trip to the DB to fetch a status, fetching rows by ID is extremely fast, and also provides a validation that your methods are only using legal values.

Objects in relation change when saving self-referencing many-to-many relationship entity framework core

I’m working on a system which revolves around objects called Units. The structure between these Units are constantly changing and I must keep track which predecessors and successors these objects have.
So I have made a self-referencing many-to-many relationship according to this post
For context, my code:
The Unit Entity:
{
public int UnitId { get; set; }
...
public IList<HistoricalUnitRelation> Predecessors { get; set; }
public IList<HistoricalUnitRelation> Successors { get; set; }
}
The class representing the join table:
{
public int PrecedingUnitId { get; set; }
public Unit PrecedingUnit { get; set; }
public int SucceedingUnitId { get; set; }
public Unit SucceedingUnit { get; set; }
}
And the Fluent API config:
builder.Entity<HistoricalUnitRelation>()
.HasKey(hisur => new { hisur.PrecedingUnitId, hisur.SucceedingUnitId });
builder.Entity<HistoricalUnitRelation>()
.HasOne<Unit>(hisur => hisur.PrecedingUnit)
.WithMany(u => u.Predecessors)
.HasForeignKey(hisur => hisur.PrecedingUnitId)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<HistoricalUnitRelation>()
.HasOne<Unit>(hisur => hisur.SucceedingUnit)
.WithMany(u => u.Successors)
.HasForeignKey(hisur => hisur.SucceedingUnitId)
.OnDelete(DeleteBehavior.Restrict);
I should also mention that I'm using a PostgreSQL (12.2) Database with The Postgis (2.5.4) extention. (and the npgsql EF Core provider of course)
This is all migrated without any problems but I'm getting some real weird behavior when trying to save objects using this relation.
Code I'm using to save the Unit objects:
if (unitDTO.PredecessorIds != null)
{
List<HistoricalUnitRelation> predecessors = new List<HistoricalUnitRelation>();
List<Unit> predecessorUnitObjects = _context.Units.Where(u => unitDTO.PredecessorIds.Contains(u.UnitId)).ToList();
foreach (Unit predecessorUnitObject in predecessorUnitObjects)
{
HistoricalUnitRelation historicalUnitRelation = new HistoricalUnitRelation() { PrecedingUnit = predecessorUnitObject , SucceedingUnit = unit };
predecessors.Add(historicalUnitRelation);
}
unit.Predecessors = predecessors;
}
The code above should find the desired units and add them to the new Unit's predecessor list.
Everything seems to be working as intended until it's time to save the new object:
imgur link to object pre-save
Pre saving the object everything looks as it should be, unit with ID 36 should be the predecessor and the newly created object should be the successor.
imgur link to object post-save
After saving the object something weird happens; the newly created unit with ID 40 has become both the predecessor AND the successor. Something somewhere must've gone wrong but I can't seem to pinpoint what exactly causes this behavior. The most curious thing of all is that I already have defined multiple many-to-many relationships, albeit between different objects, and those seem to work perfectly.

Auto-generated FK relations in EF Core - how to made them non-nullable

I have the following model:
public class Child
{
public int Id { get; set; }
}
public class Parent
{
public int Id { get; set; }
public List<Child> Childs { get; set; }
}
Without any further instructing, EF Core 3.1 automatically infers the reference relation between Parent and Child, and generates the following migration creating nullable foreign key column on Child table:
....
migrationBuilder.CreateTable(
name: "Child",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
ParentId = table.Column<int>(nullable: true) // <--- !!
},
constraints: table =>
{
table.PrimaryKey("PK_Child", x => x.Id);
table.ForeignKey(
name: "FK_Child_Parent_ParentId",
column: x => x.ParentId,
principalTable: "Parent",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
resulting in the schema below:
I need the FK to be non-nullable though. How to enforce EF to do it without changing the model (without the necessity of introducing artificial properties only to define underlying storage relations)?
PS: In particular I want to avoid abusing the model by introducing 2-way references, only to be able to express what I need, e.g.
public class Child
{
public int Id { get; set; }
public Parent Parent { get; set; } // <--- not acceptable
}
modelBuilder.Entity<Parent>()
.HasMany(p => p.Childs)
.WithOne(c => c.Parent)
.IsRequired(); // <--- non-null
Is the manual interference into the migration code the only solution (doesn't it result in mismatch with the model snapshot then)?
Since the dependent entity has no reference navigation property on which to put [Required] attribute or use C# 8 non nullable reference type (e.g. Parent vs Parent?), and has no explicit FK property with non nullable type (e.g. int vs int?), the only remaining option is fluent API.
Relationship fluent API requires at least correct Has + With pair, and then in this particular case IsRequired() method:
modelBuilder.Entity<Parent>()
.HasMany(e => e.Childs) // collection navigation property
.WithOne() // no reference navigation property
.IsRequired();

Entity Framework - "eager loading" using .Include() and .Select() - how to use a LEFT JOIN rather than INNER JOIN?

Here is a query I'm working on in Entity Framework 5.0.0 RC (code first) with .NET 4.0
I'm new to Entity Framework, so I'm still getting my head around how to structure the queries, particularly around selecting "child" related data.
I'm using "eager loading" so I get all of the related data at once. But I'm having a problem in that not all of the Drops are being retrieved.
var loads = context.Loads
.Include(
p => p.Device
)
.Include(
p => p.Drops.Select(
a => a.Customer
)
).Include(
q => q.Drops.Select(
b => b.Items.Select(
c => c.Product
)
)
).Where(
u => u.Id.Equals(id)
);
The problem is that in the generated SQL query, the Customers are being INNER JOINED to the Drops, thus excluding Drops which don't have a Customer.
So how do I make it do a LEFT JOIN between those two entities?
.Include appears to do left joins - so why not .Select ?
Is there a method other than .Select that I can use which will do a LEFT JOIN ?
UPDATE
After chatting with Amiram I realised that I had set my Drop model up incorrectly. I needed to set the CustomerID column to be optional:
public class Drop
{
public int Id { get; set; }
public int? CustomerId { get; set; }
public int LoadId { get; set; }
public DateTime Date { get; set; }
public virtual Customer Customer { get; set; }
public virtual ICollection<DropItem> Items { get; set; }
}
I should have immediately thought of this, but to be honest I was put off by the fact that .Include() always does a LEFT JOIN, regardless of the cardinality in the relationship of the models. I was thinking .Select() must have some similar behaviour, but no it was just obeying how the model was configured :)
The drops are inner joined with customers since Drop.CustomerID is of type int and not nullable int (Look in the chat).

Ordering Entity Framework items and child items for MVC view

How can I sort a query from a DbSet and include child items which should also be sorted.
Example:
I have a model for scheduling orders.
public class Order
{
public virtual int Id { get; set; }
public virtual int? SchedulingOrder { get; set; }
public virtual int? WeekId { get; set; }
public virtual Week Week { get; set; }
}
public class Week
{
public virtual int Id { get; set; }
public virtual DateTime StartDate { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
...
public DbSet<Week> Weeks { get; set; }
public DbSet<Order> Orders { get; set; }
Then an action method
public ActionResult ShopSchedule()
{
return View(db.Weeks.OrderBy(w => w.StartDate)
.Include(w => w.Orders.OrderBy(o => o.SchedulingOrder))
.ToList());
}
This doesn't work I think because of the nature of Include. Do I have to create a separate view model and map to it? Or is there some way to get around it right there in the query? There is some kind of syntax where people say new { left = right, etc } within the query?
related questions:
Ordering Entity Framework sub-items for EditorFor
C# Entity Framework 4.1 Lambda Include - only select specific included values
It's worth noting that the other 2 solutions here pull the data via SQL, then reorder things in memory, which is very wasteful in terms of performance during both the query and the post-processing. This solution gets things in one go via SQL alone, without the extra in-memory step.
It can be done as described in the second approach here:
How to order child collections of entities in EF
Like:
db.VendorProducts.Select(p =>
new { Product = p, S = p.Schedules.OrderBy(s => s.From) })
.FirstOrDefault(q => q.Product.Id == id).Product
So instead of an Include statement, you call up the related data in an anonymous object along with the original root data you were going to fetch, order it in that subquery and finally return the root data. The ordering remains intact. Twisted but it works.
To stick with your original code:
db.Weeks.Select(w => new { W = w, O = w.Orders.OrderBy(o => o.SchedulingOrder) })
.OrderBy(q => q.W.StartDate).Select(q => q.W);
You are right, you can't use orders in Include, it's not meant to work that way. But you could sort the results within the view using the OrderBy on the Orders collection. Also, you're returning a result directly, shouldn't it be return View(db.Weeks...);
Something like this should work :
public ActionResult ShopSchedule()
{
var vw = db.Weeks.OrderBy(w => w.StartDate)
.Include(w => w.Orders)
.ToList();
vw.Orders = vw.Orders.OrderBy(o => o.SchedulingOrder).ToList()
return view(vw);
}

Resources