Adding new members to a DbSet<Class> in ASP.NET MVC 4 - c#

Here is my scenario: I have a class called Order, which consists of basic information that should be saved in the database after that an order has been made.
In MyPoject.Infrastructure I use following code:
public class ProductDb : DbContext, IProductDataSource
{
public ProductDb()
: base("DefaultConnection")
{
}
public DbSet<Order> Orders { get; set; }
IQueryable<Order> IProductDataSource.Orders
{
get
{
return Orders;
}
set
{
Orders = (DbSet<Order>)value;
}
}
}
In the Controller, I add this:
private IProductDataSource _db = new ProductDb();
public UserController(IProductDataSource db)
{
_db = db;
}
Later on, in the ActionResult, where I want to add data to the order I use following:
var orders = _db.Orders;
var order = new Order();
//add some data to the order variable
_db.Orders.AsEnumerable().Concat(new[] { order });
_db.Save();
However, this does not appear to work. The problem I face is how it is possible to add new items to Order in the database.
EDIT:
IProductDataSource contains following code
public interface IProductDataSource
{
IQueryable<UserProfile> UserProfiles { get; set; }
IQueryable<Order> Orders { get; set; }
void Save();
void Add();
//void Add();
}

Looking at your code, it appears you are using an Interface to create an IQueryable<Order>, presumably to not expose the rest of your systems to Entity Framework. However, an IQueryable<T> is just a special version of IEnumerable<T>, it still does not have access to Entity Framework features.
If you don't mind exposing your DBset<Order>, it is much easier to work that way since DBSet<T> has full support of Entity Framework behind it and supports add, delete, etc.
However, if you don't want to work with a DBSet<T> for some reason, you will need to create your own Add method which takes in your new Order, creates a temporary DBSet<Order>, adds the Order to the DBSet, then saves the changes. You cannot Add directly to the IQueryable<Order>.

You need to do this:
var orders = _db.Orders;
var order = new Order();
//add some data to the order variable
_db.Orders.Add(order); // order is your new Order
_db.Save();

Related

Entity Framework Core get data from stored procedure and then convert into view model without DbSet

I have a function in the repository, GetForms, the purpose of the function is to call a stored procedure and return rows with data. Everything is working fine until now.
Function
public IEnumerable<FormBO> GetForms()
{
var id = "1"
var Query= _context.FormBO.FromSqlRaw("dbo.SP_Core #pin_ID={0}", id)
.AsNoTracking().ToList(); //3K line of sp
return Query;
}
Model
public class FormBO
{
[Key]
public int? ID { get; set; }
public int? secondid { get; set; }
......
}
DbContext
Added this code, so context thinks it is a table in the database and, I don't have to do more stuff
public virtual DbSet<FormBO> FormBO { get; set; }
The problem
Whenever we scaffold the database and the db context, it regenerates all the files and code, so it removes the
public virtual DbSet<FormBO> FormBO { get; set; }
And we have to add this line manually is there any way I can change the logic, so I don't have to add this code (DBset<FormBO>) to DbContext every time a dba updates the database...
What I found
I found that if I change the model to ".Database" and FromSqlRaw to ExecuteSqlRaw, but it is just returning the count as int not a list of rows.
public IEnumerable<FormBO> GetForms()
{
var id = "1"
var Query = _context.Database.ExecuteSqlRaw("dbo.SP_Core #pin_ID={0}", id)
.AsNoTracking().ToList(); //3K line of sp
return Query;
}
If it is possible it automatically add the DBSet to context whenever we update the code which I don't think we will able to do.
or
Get the query result without the dbset model and then I will use foreach loop to add it in FormBO model it is just 10 rows
Since the table doesn't actually exist in the database, the built in scaffolding process won't attempt to create it.
However you could probably replace the IScaffoldingModelFactory service, with an implementation that extends RelationalScaffoldingModelFactory, and use the code-first fluent api to define meta data for tables that don't really exist.
You could probably use this type of approach to define types for all table values in the database. Since EF Core 5 is adding support for table values, maybe they'll do it for you, but I haven't tested that.
public class MyModelFactory : RelationalScaffoldingModelFactory
{
public MyModelFactory(
IOperationReporter reporter,
ICandidateNamingService candidateNamingService,
IPluralizer pluralizer,
ICSharpUtilities cSharpUtilities,
IScaffoldingTypeMapper scaffoldingTypeMapper,
LoggingDefinitions loggingDefinitions)
: base(reporter, candidateNamingService, pluralizer, cSharpUtilities, scaffoldingTypeMapper, loggingDefinitions)
{
}
protected override ModelBuilder VisitDatabaseModel(ModelBuilder modelBuilder, DatabaseModel databaseModel)
{
modelBuilder.Entity<FormBO>(entity =>
{
// ...
});
return base.VisitDatabaseModel(modelBuilder, databaseModel);
}
}
services.AddDbContextPool<ContextType>(o =>
{
o.ReplaceService<IScaffoldingModelFactory, MyModelFactory>();
// ...
});
Of course there's an easy answer too. The scaffolded context is a partial class. Just define your other DbSet in another source file.

Populate nested ICollection within viewmodel

Im trying to figure out a nice way to populate a nested collection model. I have the following in my viewmodel.
public class ListViewModel
{
public ICollection<Wish> Wishes { get; set; }
}
The Wish model looks like this:
public class Wish
{
public ICollection<Image> Images { get; set; }
}
Now in my controller I want to populate the ListViewModel with wishes also populate each wish with their corresponding images. What I have so far:
public IActionResult Index()
{
ICollection wishes = _repoWish.GetAllAsync().Result;
ICollection images = _repoImage.GetAllAsync().Result;
var model = new ListViewModel
{
Wishes = wishes
};
return View(model);
}
I know I can make a lot of foreach statements but I want to make use of LINQ to populate each wish with their corresponding images.
**I do have a generic repository class which makes it possible for me to retrieve all images in the same manner as the wishes.
*** Think about the repositories as contexts.
So instead of _repoWish and _repoImage its wishContext and imageContext
I am using ASP.NET Core 2.0 with Entity Framework Core
To load the related entities, you need to explicitly use the Include method call when you query the Wishes collection to eager load the Images property.
Also make sure you await your async calls.
var wishesWithImages = await yourDbContext.Wishes
.Include(g => g.Images)
.ToListAsync();
The variable wishesWithImages will be a collection of Wish objects with Images property loaded. You can now use that to populate your view model.
var vm = new ListViewModel { Wishes = wishesWithImages };
Assuming your Wish entity has a collection property of type Images
public class Wish
{
public int Id { set;get;}
public ICollection<Image> Images { set;get;}
}
public class Image
{
public int Id { set;get;}
public int WishId { set;get;}
public virtual Image Image{ set;get;}
}
As of today, Entity framework core is a light weight version of EF6 and doesn't automatically inherit all the features from EF 6. Lazy loading is not implemented yet in EF core.

Fluent NHibernate mapping with HasMany which fails to return child collection

So I created a new simple project just to help a friend.
So I made a class Customer which has a list of Stuff
So far so good, now with the mapping and storing the relationsship. I went to map in accordance with fluent nhibernate class maps and ended up with the following
public class CustomerMap : ClassMap<Customer> {
Id(p => p.Id).GenerateBy.Guid();
HasMany(p => p.Stuff).Access.CamelCaseField().KeyColumn("Id").Inverse().Cascade.AllDeleteOrphan();
}
public class StuffMap : ClassMap<Stuff> {
Id(p => p.Id).GeneratedBy.Guid();
Reference(p => p.Customer).Column("CustomerId).Not.Nullable();
}
and my classes
public class Customer {
private ISet<Stuff> stuff = new HashSet<Stuff>()
public virtual IEnumerable<Stuff> Stuff => stuff;
public void AddStuff(Stuff newstuff) {
newstuff.Customer = this;
stuff.Add(stuff);
}
}
public class Stuff {
public virtual Customer Customer { get; set; }
}
All this works good and when I create a new Customer and add one of more Stuff elements into the collection using the method AddStuff and commits the transaction it gets correctly written to the database.
However now the strange begins, when I make a test like the following
[TestMethod]
public void TestStuffAndCustomer() {
var customer = session.Add(new Customer());
customer.AddStuff(new Stuff());
session.Flush();
var customer = session.Query<Customer>().Single();
customer.Stuff.Should().HaveCount(1);
}
The assertion of the collection fails with reason that the count of the collection is 0. However if I debug the test and check the collection it contains one element. The assertion fails regardless however.
So what is wrong with this setup?
I think you add new Customer and Stuff to Customer on session, but without saving them you flush the session.

Entity Framework: Best way to delete an entity when another entity gets deleted?

I have orders and orders have lines. When one deletes the last order line the order should be deleted as well. I'm struggling to find the best place for this.
The developer would just call context.Remove(orderLine) to remove the line. So the logic to then delete the order if this was the last line should be in the remove call.
The current idea would be to create a OrderLineDbSet which inherits from the DbSet and overwrite the Remove call there. But the issue is, that I don't have access to the DataContext because dependency injection does not work here...
Id did a bit of digging into https://github.com/mono/entityframework/blob/master/src/EntityFramework/DbSet.cs but I couldn't figure it out.
The last code i tried was kind of this:
public class OrderLineDbSet : DbSet<OrderLine>
{
CourseContext context { get; set; }
public OrderLineDbSet(CourseContext context)
{
this.context = context;
}
public override OrderLine Add(OrderLine entity)
{
return base.Add(entity);
}
public override OrderLine Remove(OrderLine entity)
{
Order order = entity.Order;
var line = base.Remove(entity);
if (!order.OrderLines.Any())
{
context.Orders.Remove(order);
}
return line;
}
}
The problem is that you're trying to push business logic into data access layer. Usually, you have a class, which implements some business logic, e.g.:
public class OrdersService
{
public void RemoveOrderLine(OrderLine orderLine)
{
// get db context (or some repository)
var context = GetDbContext();
// attach or load entities, etc.
// this is _business logic_;
// it is not natural for relational database;
// it is not related to db context or repositiory
context.OrderLines.Remove(orderLine);
if (!order.OrderLines.Any())
{
context.Orders.Remove(order);
}
}
}
In other words. Imagine, that after last line was removed, user must receive SMS, that order was removed too. Here's an action, that is totally unrelated to database. Do you want to put SMS sending in DbSet?
This should be set up with mapping and let entity framework remove the whole object graph.
public class MyContext : DbContext
{
public DbSet<Order> Orders { get; set; }
public DbSet<OrderLine> Lines { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.HasMany(a => a.Lines)
.WithRequired(b => b.Orders)
.WillCascadeOnDelete();
}
}

Entity framework creates new record when SaveChanges is called

I have two entities:
public class Order:Entity
{
public virtual User User { get; set; }
...
}
public class User:Entity
{
public virtual ICollection<Order> Orders { get; set; }
...
}
Next, I create order:
var order = _orderService.CreateTransientOrder(orderNumbers, PcpSession.CurrentUser);
PcpSession.Order = order;
this is CreateTransientOrder. It's only create Order, but not save into database:
public Order CreateTransientOrder(string orderNumbers, User currentUser)
{
...fill fields
order.User = currentUser;
return order;
}
Now it's all ok. Next, I save order to the database:
_orderService.CreateOrder(PcpSession.Order);
This is CreateOrder:
public void CreateOrder(Order order)
{
order.OrderDate = DateTime.Now;
_repository.Save(order);
_repository.SaveChanges();
}
This is my Save method of repository:
public void Save<T>(T entity) where T : class, IEntity
{
_context.Set<T>().Add(entity);
}
When the SaveChanges is called in the database creates new user with new ID and order have new User_Id. In the debugger in the CreateOrder method, Id is equal current user. Where is a problem?
Thanks.
User is probably not being tracked by the context. When you add order to the context it also adds the related entities and then on save changes creates a new user (or attempts to). Attach() the user to the context before you call _context.Set<T>().Add(entity);.
I guess the problem is not related with the code you have provided. It seems to be related to where you are initializing PcpSession.CurrentUser.
It seems PcpSession.CurrentUser object is not attached to the context. Either fetch this entity to the context before making you Order related calls or attach it.
You need attach your Entity if not attach in context.
for exemple in Repository Generic
> public void Add(T entity)
> {
> entity.Create = DateTime.Now;
> db.Set<T>().Attach(entity); // Add Line
> db.Set<T>().Add(entity);
> Save();
> }
I do not know if it's clean but it regulates the problem

Categories