I have a report that I need to send to my react frontend that needs to be easily queried and searched. The problem is with the current method we need to pull all the entire database before performing a query due to nested objects and other factors.
To significantly speed up the process I want to create a Report Table/View to query from that stays up to date as the other tables change.
Here is a small example of the models:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int TypeId { get; set; }
public ItemType Type { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; }
}
public class ItemType
{
public int Id { get; set; }
public string Name { get; set; }
public List<Item> Items { get; set; }
}
public class Order
{
public int Id { get; set; }
public Customer Customer { get; set; }
public List<Item> Items { get; set; }
}
public class ItemReport
{
public string Name { get; set; }
public string Description { get; set; }
public string Type { get; set; }
public string Customer { get; set; }
public ItemReport(Item item)
{
Name = item.Name;
Description = item.Description;
Type = item.Type.Name;
Customer = item.Order.Customer.Name;
}
}
ItemReport is the model I use to send to the frontend.
I've read a fair amount on Keyless Entities and Views, but am in need of a little guidance on putting all the pieces together.
Currently we would pull all Items and required fields like Name from customer and turn it into an IEnumerable list of ItemReport to then be filtered/sorted or searched.
As a side note, there may be other solutions than the on I'm posting for that I would be open to as well.
I've looked into this quite a bit, but I don't think I'm finding the right solutions as an example when reading on Views they mention how they cannot be inserted into or updated with EF Core.
It is simple projection. Passing item in Constructor is not right way, because EF Core cannot look into compiled method body.
var query = context.Items
.Select(item => new ItemReport
{
Name = item.Name;
Description = item.Description;
Type = item.Type.Name;
Customer = item.Order.Customer.Name;
});
I have a Supplier.cs Entity and its ViewModel SupplierVm.cs. I am attempting to update an existing Supplier, but I am getting the Yellow Screen of Death (YSOD) with the error message:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
I think I know why it is happening, but I'm not sure how to fix it. Here's a screencast of what is happening. I think the reason I'm getting the error is because that relationship is lost when AutoMapper does its thing.
CODE
Here are the Entities that I think are relevant:
public abstract class Business : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string TaxNumber { get; set; }
public string Description { get; set; }
public string Phone { get; set; }
public string Website { get; set; }
public string Email { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public virtual ICollection<Address> Addresses { get; set; } = new List<Address>();
public virtual ICollection<Contact> Contacts { get; set; } = new List<Contact>();
}
public class Supplier : Business
{
public virtual ICollection<PurchaseOrder> PurchaseOrders { get; set; }
}
public class Address : IEntity
{
public Address()
{
CreatedOn = DateTime.UtcNow;
}
public int Id { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string Area { get; set; }
public string City { get; set; }
public string County { get; set; }
public string PostCode { get; set; }
public string Country { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public int BusinessId { get; set; }
public virtual Business Business { get; set; }
}
public class Contact : IEntity
{
public Contact()
{
CreatedOn = DateTime.UtcNow;
}
public int Id { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string Department { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public int BusinessId { get; set; }
public virtual Business Business { get; set; }
}
And here is my ViewModel:
public class SupplierVm
{
public SupplierVm()
{
Addresses = new List<AddressVm>();
Contacts = new List<ContactVm>();
PurchaseOrders = new List<PurchaseOrderVm>();
}
public int Id { get; set; }
[Required]
[Display(Name = "Company Name")]
public string Name { get; set; }
[Display(Name = "Tax Number")]
public string TaxNumber { get; set; }
public string Description { get; set; }
public string Phone { get; set; }
public string Website { get; set; }
public string Email { get; set; }
[Display(Name = "Status")]
public bool IsDeleted { get; set; }
public IList<AddressVm> Addresses { get; set; }
public IList<ContactVm> Contacts { get; set; }
public IList<PurchaseOrderVm> PurchaseOrders { get; set; }
public string ButtonText => Id != 0 ? "Update Supplier" : "Add Supplier";
}
My AutoMapper mapping configuration is like this:
cfg.CreateMap<Supplier, SupplierVm>();
cfg.CreateMap<SupplierVm, Supplier>()
.ForMember(d => d.Addresses, o => o.UseDestinationValue())
.ForMember(d => d.Contacts, o => o.UseDestinationValue());
cfg.CreateMap<Contact, ContactVm>();
cfg.CreateMap<ContactVm, Contact>()
.Ignore(c => c.Business)
.Ignore(c => c.CreatedOn);
cfg.CreateMap<Address, AddressVm>();
cfg.CreateMap<AddressVm, Address>()
.Ignore(a => a.Business)
.Ignore(a => a.CreatedOn);
Finally, here's my SupplierController Edit Method:
[HttpPost]
public ActionResult Edit(SupplierVm supplier)
{
if (!ModelState.IsValid)
return View(supplier);
_supplierService.UpdateSupplier(supplier);
return RedirectToAction("Index");
}
And here's the UpdateSupplier Method on the SupplierService.cs:
public void UpdateSupplier(SupplierVm supplier)
{
var updatedSupplier = _supplierRepository.Find(supplier.Id);
Mapper.Map(supplier, updatedSupplier); // I lose navigational property here
_supplierRepository.Update(updatedSupplier);
_supplierRepository.Save();
}
I've done a load of reading and according to this blog post, what I have should work! I've also read stuff like this but I thought I'd check with readers before ditching AutoMapper for Updating Entities.
The cause
The line ...
Mapper.Map(supplier, updatedSupplier);
... does a lot more than meets the eye.
During the mapping operation, updatedSupplier loads its collections (Addresses, etc) lazily because AutoMapper (AM) accesses them. You can verify this by monitoring SQL statements.
AM replaces these loaded collections by the collections it maps from the view model. This happens despite the UseDestinationValue setting. (Personally, I think this setting is incomprehensible.)
This replacement has some unexpected consequences:
It leaves the original items in the collections attached to the context, but no longer in scope of the method you're in. The items are still in the Local collections (like context.Addresses.Local) but now deprived of their parent, because EF has executed relationship fixup. Their state is Modified.
It attaches the items from the view model to the context in an Added state. After all, they're new to the context. If at this point you'd expect 1 Address in context.Addresses.Local, you'd see 2. But you only see the added items in the debugger.
It's these parent-less 'Modified` items that cause the exception. And if it didn't, the next surprise would have been that you add new items to the database while you only expected updates.
OK, now what?
So how do you fix this?
A. I tried to replay your scenario as closely as possible. For me, one possible fix consisted of two modifications:
Disable lazy loading. I don't know how you would arrange this with your repositories, but somewhere there should be a line like
context.Configuration.LazyLoadingEnabled = false;
Doing this, you'll only have the Added items, not the hidden Modified items.
Mark the Added items as Modified. Again, "somewhere", put lines like
foreach (var addr in updatedSupplier.Addresses)
{
context.Entry(addr).State = System.Data.Entity.EntityState.Modified;
}
... and so on.
B. Another option is to map the view model to new entity objects ...
var updatedSupplier = Mapper.Map<Supplier>(supplier);
... and mark it, and all of its children, as Modified. This is quite "expensive" in terms of updates though, see the next point.
C. A better fix in my opinion is to take AM out of the equation completely and paint the state manually. I'm always wary of using AM for complex mapping scenarios. First, because the mapping itself is defined a long way away from the code where it's used, making code difficult to inspect. But mainly because it brings its own ways of doing things. It's not always clear how it interacts with other delicate operations --like change tracking.
Painting the state is a painstaking procedure. The basis could be a statement like ...
context.Entry(updatedSupplier).CurrentValues.SetValues(supplier);
... which copies supplier's scalar properties to updatedSupplier if their names match. Or you could use AM (after all) to map individual view models to their entity counterparts, but ignoring the navigation properties.
Option C gives you fine-grained control over what gets updated, as you originally intended, instead of the sweeping update of option B. When in doubt, this may help you decide which option to use.
I searched all stackoverflow answers and google searches. Finally i just added 'db.Configuration.LazyLoadingEnabled = false;' line and it worked perfectly for me.
var message = JsonConvert.DeserializeObject<UserMessage>(#"{.....}");
using (var db = new OracleDbContex())
{
db.Configuration.LazyLoadingEnabled = false;
var msguser = Mapper.Map<BAPUSER>(message);
var dbuser = db.BAPUSER.FirstOrDefault(w => w.BAPUSERID == 1111);
Mapper.Map(msguser, dbuser);
// db.Entry(userx).State = EntityState.Modified;
db.SaveChanges();
}
I've gotten this issue many times and is normally this:
The FK Id on the parent reference doesn't match the PK on that FK entity. i.e. If you have an Order table and a OrderStatus table. When you load both into entities, Order has OrderStatusId = 1 and the OrderStatus.Id = 1. If you change OrderStatusId = 2 but do not update OrderStatus.Id to 2, then you'll get this error. To fix it, you either need to load the Id of 2 and update the reference entity or just set the OrderStatus reference entity on Order to null before saving.
I am not sure if this is going to fit your requirement but I would suggest following.
From your code it surely looks like you are loosing relationship during mapping somewhere.
To me it looks like that as part of UpdateSupplier operation you are not actually updating any of the child details of the supplier.
If that is the case I would suggest to updadate only changed properties from the SupplierVm to the domain Supplier class. You can write a separate method where you will assign property values from SupplierVm to the Supplier object (This should change only non-child properties such as Name, Description, Website, Phone etc.).
And then perform db Update. This will save you from possible messup of the tracked entities.
If you are changing the child entities of supplier, I would suggest to update them independent of suppliers because retrieving an entire object graph from database would require lot of queries to be executed and updating it will also execute unnecessary update queries on database.
Updating entities independently would save lot of db operations and would add to the performance of the application.
You can still use the retrieval of entire object graph if you have to display all the details about the supplier in one screen. For updates I would not recommend update of entire object graph.
I hope this would help resolving your issue.
In my IdentityModels class I have a property called Reports like below
public DbSet<Report> Reports { get; set; }
In my database there is a table with the same name which this property pulls the data from.
I also have a model called Report that (in addition to some other properties) has these
public class Report
{
//...
[Required]
public string State { get; set; }
[Column("reporter_id")]
public string ReporterId { get; set; }
[Required]
[Column("report_text")]
public string ReportText { get; set; }
//...
}
I also have a view that is a strong view with Report as its model.
Where I ran into a problem is that my model has reporterId which is a foreign key to different table called AspNetUsers that has the user details.
I want to display the user name not the id and because the Reports table only has the userId there is no way for me to display the user name.
What would be a good way of making a user_name field in the AspNetUsers table be part of my model? There obviously has to be a join statement somewhere between the reports table and aspNetUsers table but I'm not sure how best to do this.
My answer may not be helpful to you but I'm will still post it, ViewModel are specifically used for this kind of situation where you need to have two or more tables data displayed in a view.
First , you will need to create a ViewModel lets call it UserReportViewModel. In this view model, you will include an additional property UserName that will pull the username based on ReporterId.
public class UserReportViewModel
{
[Required]
public string State { get; set; }
[Column("reporter_id")]
public string ReporterId { get; set; }
[Required]
[Column("report_text")]
public string ReportText { get; set; }
public string UserName{ get; set; }
}
Then , assuming you are using LINQ to retrieve the report:
Dim ReportList As List(Of UserReportViewModel) = New List(Of UserReportViewModel)
ReportList = dbContext.Reports.Select(Function(x) New UserReportViewModel With {
.State = x.State, _
.ReporterId= x.ReporterId, _
.UserName= dbContext.Users.Find(x.ReporterId).UserName, _
.ReportText = x.ReportText, _
}).ToList()
You have to change your models as shown below.
public class Report
{
[Required]
public string State { get; set; }
[ForeignKey("UserId")]
public virtual AspNetUser { get; set; }
public int UserId { get; set; }
[Required]
[Column("report_text")]
public string ReportText { get; set; }
}
public class AspNetUser
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Report> Reports { get; set;}
}
Your Query should be like this :
var query =
(from u in db.AspNetUsers
from r in u.Reports
select new { UserName = u.Name, ReportText = r.ReportText }).ToList();
Here are four different approaches to defining an Entity class in Entity Framework. Can someone tell me what is the difference in the way each approach works and also recommend which of these approaches to use?
// Approach 1
public class User
{
public int Id { get; set; }
public Address Address { get; set; }
}
// Approach 2
public class User
{
public int Id { get; set; }
public Address Address { get; set; }
public User()
{
this.Address = new Address();
}
}
// Approach 3
public class User
{
public int Id { get; set; }
public virtual Address Address { get; set; }
}
// Approach 4
public class User
{
public int Id { get; set; }
public virtual Address Address { get; set; }
public User()
{
this.Address = new Address();
}
}
Can I please ask for any good explanation of the differences?
Are the differences related to Lazy loading vs. Eager loading?
Which is better and why?
Here is how it should look like:
public class User
{
public int Id { get; set; }
public int AddressId { get; set; }
public virtual Address Address { get; set; }
}
Explanations:
We need to mark our navigation properties as virtual to enable EF lazy loading at runtime. EF creates a user proxy object inheriting from your user class and marking Address as virtual allows EF to override this property and add lazy loading support code.
Having an AddressId as a FK for Address navigation property essentially converts your User-Address association to a "Foreign Key Association". These type of associations are preferred since they are easier to work with when it comes to updates and modifications.
Unless you have a navigation property in the form of collection of objects (e.g. IList<Address>) you don't need to initialize it in your constructor. EF will do that for you automatically if you include them in your queries.
I have created Entity Data Model in Visual Studio. Now I have file with SQL queries and C# classes generated from Model.
Question:
Classes are generated without annotations or code behind (Fluent API). Is it OK? I tried to run my application but exception was thrown:
Unable to determine the principal end of an association between the types 'Runnection.Models.Address' and 'Runnection.Models.User'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
I read that I can not use Fluent API with "Model First". So what can I do?
Code:
User
public partial class User
{
public User()
{
this.Events = new HashSet<Event>();
this.CreatedEvents = new HashSet<Event>();
}
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Photo { get; set; }
public int EventId { get; set; }
public string Nickname { get; set; }
public OwnerType OwnerType { get; set; }
public NetworkPlaceType PlaceType { get; set; }
public virtual ICollection<Event> Events { get; set; }
public virtual Address Address { get; set; }
public virtual ICollection<Event> CreatedEvents { get; set; }
public virtual Owner Owner { get; set; }
}
Address
public partial class Address
{
public int Id { get; set; }
public string Street { get; set; }
public string StreetNumber { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
public string Country { get; set; }
public virtual User User { get; set; }
}
Context
//Model First does not use this method
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Address>().HasRequired(address => address.User)
.WithRequiredDependent();
modelBuilder.Entity<User>().HasRequired(user => user.Address)
.WithRequiredPrincipal();
base.OnModelCreating(modelBuilder);
}
You have to specify the principal in a one-to-one relationship.
public partial class Address
{
[Key, ForeignKey("User")]
public int Id { get; set; }
public string Street { get; set; }
public string StreetNumber { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
public string Country { get; set; }
public virtual User User { get; set; }
}
By specifying a FK constraint, EF knows the User must exists first (the principal) and the Address follows.
Further reading at MSDN.
Also, see this SO answer.
Updated from comments
In the designer, select the association (line between Users & Address). On the properties window, hit the button with the [...] on Referential Constraint (or double click the line). Set the Principal as User.
Error:
Had same error of "Unable to determine the principal end of an association between the types 'Providence.Common.Data.Batch' and 'Providence.Common.Data.Batch'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.".
HOWEVER, note that this is the SAME table.
Cause: My database was MS SQL Server. Unfortunately when MS SQL Server's Management Studio adds foreign keys, it adds the default foreign key as Batch ID column of Batch table linking back to itself. You as developer are suppose to pick another table and id to truly foreign key to, but if you fail to it will still allow entry of the self referencing FK.
Solution:
Solution was to delete the default FK.
Cause 2: Another situation is that the current table may be fixed but the old historical image of the table when the EF's edmx was done had the default FK.
Solution 2: is to delete the table from the Model Browser's Entity Types list and click "yes" and then "Update Model from the Database" again.