Using reverse engeneering with Scaffold-DBContext, makes virtual collections read-only - c#

Using EF Core 7, after scaffolding, the generated code looks like this:
public partial class Category
{
public Guid CategoryId { get; set; }
public Guid Title { get; set; }
public virtual ICollection<Product> Products { get; } = new List<Product>(); // <= setter missing here
}
As you can see, Products is read-only, which was not in previous versions of EF. The problem is that I can't fill it when creating a Category object:
Category cat = new Category()
{
CategoryId = Guid.NewGuid(),
Title = "",
Products = new List<Products>() {/* ... */} // <= Error happens here
}
it also happens when I'm going to customize the Products of a Category which are returned from database. The solution is to add setter in class model definition, but it will be deleted next time I scaffold and it's hard to manage. Is there any solution, or I need to turn back to EF5, or wait a while for fix?

Related

Insert entity with a collection of related entities which already exist in the database

I have two objects with a many-to-one relationship:
public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public virtual Collection<ProductInventory> ProductInventorys { get; set; } = new Collection<ProductInventory>();
}
public class ProductInventory
{
public int ProductInventoryID { get; set; }
public string ProductInventoryName { get; set; }
public int ProductID { get; set; }
public virtual Product ProductFK { get; set; }
}
I would like to add a new Product with a collection of existing ProductInventory (my API would have an input of ProductInventoryID array) into the database, so I perform like:
private void AddProduct(int[] productInventoryIDs)
{
Product newProduct = new Product();
newProduct.Name = "New Product";
// Here I have no clue which would be the correct way...should I use
// Approach A - fetch each related "ProductInventory" entity from database,
// then add them into the collection of my new base entity - Product)
productInventoryIDs.ToList().Foreach(p =>
{
newProduct.ProductInventorys.Add(_dbContext.ProductInventory.FindById(p))
}
);
_dbContext.Products.Add(newProduct);
_dbContext.SaveChanges();
// Approach B: Save base "Product" entity first, then grab the new ProductID,
// then fetch each "ProductInventory" from database and assign the foreign key with the new "ProductID" value, and then save each
_dbContext.Products.Add(newProduct);
var newProductID = _dbContext.SaveChanges();
productInventoryIDs.ToList().Foreach(pi =>
{
var existedProductInventoryFromDb = _dbContext.ProductInventory.FindById(pi);
existedProductInventoryFromDb.ProductID = newProductID;
_dbContext.SaveChanges();
}
);
}
By using approach (A), my newProduct failed to save and I looked into SQL resource, looks like it is trying to insert ProductInventory as well, although these ProductInventory already exist in the database. I guess that's because I add them into my base entity's collection?
By using approach (B), I am feeling a little awkward for doing that as it's like fetching and saving multiple times for just one object, I doubt if I am doing the correct way...
Maybe I am wrong at both approaches, so what would be the correct way to deal with above scenario?

Update list in Entity Framework model

I have an Entity Framework model:
public class Application
{
[Key]
public int ApplicationID { get; set; }
public int PatentID { get; set; }
...
//------------------------------------
public string ApplicationNumber { get; set; }
public string Priority { get; set; }
public List<ApplicationPayment> Payments { get; set; }
= new List<ApplicationPayment>();
}
and payment's model:
public class ApplicationPayment
{
[Key]
public int PaymentID { get; set; }
public string PaymentName { get; set; }
public float Amount { get; set; }
public int PayNumber { get; set; }
public DateTime Date { get; set; } = new DateTime(2017, 12, 1);
public float TopicPart { get; set; }
}
Entity Framework creates additional foreign keys for me in ApplicationPayment model Application_ApplicationID.
I add a new instance in ApplicationPayment table that has number of the existing Application:
But when I try to display this ApplicationPayment's table this returns the empty table.
I tried to add ApplicationPayment manually through SQL Server Management Studio and via fake-request. New line added but the list of ApplicationPayment is still empty.
Fake-request:
[HttpPut]
public void CreateApplicationPayment(int? id)
{
ApplicationPayment appPayment = new ApplicationPayment()
{
Amount = 80.0f,
Date = new DateTime(2017, 10, 25),
PaymentName = "payment",
PayNumber = 30,
TopicPart = 20
};
Application application = db.Applications.Find(id);
application.Payments.Add(appPayment);
db.SaveChanges();
}
Your collection property needs to be virtual if you want EF to automatically populate it:
public virtual List<ApplicationPayment> Payments { get; set; }
Also, if you're using EF 6 or previous, you'll need to make the type of that property ICollection<ApplicationPayment>, rather than List<ApplicationPayment>. I think EF Core relaxed this restriction, but I'm not sure. So, if you still have issues, change it there as well.
However, this is what's called lazy-loading, and it's not ideal in most scenarios. Additionally, if you're using EF Core, it still won't work, because currently EF Core does not support lazy loading. The better method is to eagerly load the relationship. This is done by using Include in your LINQ query:
Application application = db.Applications.Include(m => m.ApplicationPayments).SingleOrDefault(m => m.Id == id);
This will cause EF to do a join to bring in the related ApplicationPayments. You need to then use SingleOrDefault rather than Find, as Find doesn't work with Include. (Find looks up the object in the context first, before hitting the database, and as a result, cannot account for related items being available.)

Populate associated entities on DBContext Find

I have a Product which needs to have some fields in multiple languages. Therefore I have made a ProductLanguage table which has the composite key and language specific fields (ProductID, LanguageID, Name).
In my Product class I tried something like this:
[Table("Product")]
public class Product
{
DBContext db = new DBContext();
public Product()
{
this.Multimedias = new List<Multimedia>();
this.ProductLanguages = new List<ProductLanguages>();
this.ProductLanguage = db.ProductLanguages.Find(this.ID, Global.Language) ?? new ProductLanguage();
}
public int ID { get; set; }
public string Code { get; set; }
public virtual ICollection<Multimedia> Multimedias { get; set; }
public virtual ICollection<ProductLanguage> ProductLanguages { get; set; }
[NotMapped]
public virtual ProductLanguage ProductLanguage { get; set; }
}
So I could immediately access the language specific fields without needing to go through the collection - the problem is the object obviously doesn't have the ID yet.
Is there any way so when I do
Product product = db.Products.Find(id);
in the controller it will automatically populate my ProductLanguage property?
You can move your assignment of ProductLanguage in the Get for that property.
You don't need the property
[NotMapped]
public virtual ProductLanguage ProductLanguage { get; set; }
The ProductLanguages collection will be populated via lazy loading when you hit it. What you need is a method like this, that will return a ProductLanguage by id:
public ProductLanguage GetProductLanguageById(int id)
{
if (ProductLanguages != null)
{
return ProductLanguages.Where(pl => pl.Id == id).FirstOrDefault();
}
}
This is not confirming to any practice/usage i have seen before.., regardless I would consider it very!! very!! bad practice. Don't make an instance of your context within the Entity(table).
Also why are you doing this... I suggest you read up on Lazy and eager loading.
Quote
"in the controller it will automatically populate my ProductLanguage property?"
Yes.... use eager loading.
Product product = db.Products.Include("ProductLanguage").Find(id);
But I would highly suggest that you don't do all that other weird stuff in the initialization of your entity.
DBContext db = new DBContext();
Product product = db.Products.Include("ProductLanguage").Find(id);

Why does my ASP.NET MVC 4 application create new entities instead of updating the old ones?

EDIT: The solution I selected probably wasn't the best, but it definitely worked. I'll be going through my code over the next week (once this project is done) and I'll update my question when I understand what went wrong.
I'm using the ASP.NET MVC 4 framework with Entity 5. Here's some code:
The class to be instantiated and saved (fresh) in the database:
public class ClassCancellation
{
[Key]
public int Id { get; set; }
public Faculty Professor { get; set; }
public DateTime CancelledOn { get; set; }
public Course Course { get; set; }
[Required]
public ClassDate ClassCancelled { get; set; }
public Message CancellationMessage { get; set; }
[Required]
public List<Student> Students { get; set; }
}
It's mapped from the viewmodel called CancellationFull (with AutoMapper):
public class CancellationForList
{
[Key]
public int Id { get; set; }
public CourseForList Course { get; set; }
public ClassDateForList ClassCancelled { get; set; }
}
public class CancellationFull : CancellationForList
{
public CancellationFull()
{
this.Students = new List<StudentForList>();
}
public FacultyForList Professor { get; set; }
public MessageForList CancellationMessage { get; set; }
public DateTime CancelledOn { get; set; }
public List<StudentForList> Students { get; set; }
}
This is the repo method that turns a CancellationFull into a ClassCancellation and then saves it to the database:
public CancellationFull createClassCancellation(CancellationFull c)
{
ClassCancellation newCancellation = Mapper.Map<ClassCancellation>(c);
dc.ClassCancellations.Add(newCancellation);
dc.SaveChanges();
return Mapper.Map<CancellationFull>(dc.ClassCancellations.FirstOrDefault(cc => cc.Id == newCancellation.Id));
}
Why, for the love of god why, does the database create new objects for Faculty and Course when the Id (primary key) of each's existing entity counterpart is provided? It might also be doing the same with Student objects but I haven't looked that closely.
Before the ClassCancellation instance is saved to the database the debugger shows that it's attributes Professor of type Faculty and Course of type Course have the correct primary key - that is, the primary key of the already existing entities of those types that I'm trying to update with a reference to the new ClassCancellation object.
Driving me nuts. Feel free to ask for clarification!
EDIT:
Here's the logic where the CancellationFull viewmodel is constructed from form data and viewmodels about existing objects retrieved from their respective repos:
newCancellation = new CancellationFull();
newCancellation.CancelledOn = DateTime.Now;
newCancellation.ClassCancelled = repoClass.getClassDateForListById(Int32.Parse(classIds[i]));
newCancellation.Course = repoCourse.getForList(newCancellation.ClassCancelled.Course.Id);
newCancellation.CancellationMessage = repoMessage.getMessageForList(newMessage.Id);
newCancellation.Professor = repoFac.getFacultyForList((int)Session["facId"]);
var students = repoStudent.getStudentsForListByCourse(newCancellation.Course.Id);
foreach ( var student in students )
{
newCancellation.Students.Add(student);
}
repoCancellation.createClassCancellation(newCancellation);
Here's an example of one of those repo methods (the rest are very similar):
public CourseForList getForList(int? id)
{
return Mapper.Map<CourseForList>(dc.Courses.FirstOrDefault(c => c.Id == id));
}
What I find the easiest solution is when updating a model, clear any related entities, then re add them.
ie:
newCancellation.Students.Clear();
foreach ( var student in students )
{
newCancellation.Students.Add(student);
}
Try using Attach() instead of Add()
dc.ClassCancellations.Attach(newCancellation);
dc.SaveChanges();
Add() is used for new objects that do not already exist in the database. Attach() is used for creating relationships to entities that already exist in the database.
EDIT
Without seeing your code, the best solution I can recommend to attach is to create a 'stub' instance and then attach that to your newCancellation:
var existingCourse = new Course{ Id = newCancellation.ClassCancelled.Course.Id };
db.Courses.Attach(existingCourse);
newCancellation.Course = existingCourse;
The problem is that you have multiple contexts, or units of work. When you add the newCancellation to the dc context, it also adds any related entity in the object graph that is not tracked in the dc context. I think your best option is:
dc.ClassCancellations.Add(newCancellation);
dc.Entry(newCancellation.Course).State = EntityState.Unchanged;
dc.Entry(newCancellation.Faculty).State = EntityState.Unchanged;
See Julie Lerman's article on this issue for an explanation and other options.
In my opinion, EF should recognize entities that have autonumbered keys and not insert them if the key is assigned.

How to handle projections in RavenDB

Given domain model...
public class Entity
{
public int Id { get; set; }
public Category Category { get; set; }
}
public class Category
{
public string Title { get; set; }
}
... I want to project results of a select query to this view model:
public class EntityViewModel
{
public int Id { get; set; }
public string CategoryTitle { get; set; }
}
I have tried the following query:
var viewModel = (from entity in _documentSession.Query<Entity>()
select new EntityViewModel
{
Id = entity.Id,
CategoryTitle = entity.Category.Title
}.ToList();
The result of this is only partially correct: the Id is set, the CategoryTitle is not. I understand this behaviour is by design, but I suspect there is an API to handle this scenario.
How should such a projection be handled in RavenDB?
Update: I am using build 1.0.573 in embedded mode.
Updated 2: I have forked RavenDB repository, added a failing test to demonstrate this behaviour and created a pull request (#444). Will post more info as I find out.
Looks like it is actually a bug. See pull request #444 for more information.
I will update this answer when this is fixed in a stable release.
Fixed in the current stable release.

Categories

Resources