Best Practice with MVC4 and EF5 to apply changes - asp.net-mvc-4

I have a CustomerOrder-view where I would like to change an existing CustomerOrder.
I have a viewmodel that very simpliefied looks something like this:
public class CustomerOrderViewModel
{
public int ID { get; set; }
public Customer Customer { get; set; }
public DateTime Date { get; set; }
public IEnumerable<OrderRow> OrderRows { get; set; }
}
public class OrderRow
{
public int id { get; set; }
public int price { get; set; }
}
public class Customer
{
public string Name { get; set; }
}
I also have a database with mapping tables / fields.
In my GET Action Method I load the Order with the help of Automapper like this:
var customerOrder = using (var ctx = new My_Entities()) {
return ctx.CustomerOrders.
Include("Orderrows").
Include("Customer").
Single(o => o.CustomerOrderID == id);
}
var model= AutoMapper.Mapper.DynamicMap<DataAccessLayer.CustomerOrder, CustomerOrderViewModel>(customerOrder);
In the View I use Knockout to bind to a viewmodel there, where the user can update the CustomerOrder. That includes editing Customer information and adding new orderrows etc.
Then in the post back a map the ViewModel back to the ObjectContext CustomerOrder:
var customerOrderToBeSaved =
AutoMapper.Mapper.DynamicMap<CustomerOrderViewModel, CustomerOrder>(
customerOrderViewModel);
try
{
using (var ctx = new MyEntities())
{
ctx.CustomerOrders.Attach(customerOrderToBeSaved);
ctx.CustomerOrders.ApplyCurrentValues(customerOrderToBeSaved);
...
ctx.SaveChanges();
}
}
I get the error message: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
OK, that I can understand. But how should I go about this? Can I get the existing object and apply Changes to that one, because that is really what I'd like. I've tried to look up the old one and detach it but I haven't got it to wrok.Perhaps I'm doing this in a completely wrong way. Please advice.

You should not attach customerOrderToBeSaved, see MSDN about the argument of ApplyCurrentValues.
The detached object that has property updates to apply to the original object.
So you've got to load the entity from the database into the context and then ApplyCurrentValues with the detached object that has the new values.

You don't have to load the row from the database to update it.
You can do something like this:
var entity = ctx.CustomerOrders.Attach(customerOrderToBeSaved);
ctx.Entry( entity ).State = EntityState.Modified;
ctx.SaveChanges();
This will tell EF to issue an UPDATE SQL statement that overwrites all the columns in the record.
You can select which columns you want to update like this:
var entity = ctx.CustomerOrders.Attach(customerOrderToBeSaved);
var entry = ctx.Entry( entity );
entry.Property( o => o.<ColumnPropertyToUpdate> ).IsModified = true;
entry.Property( o => o.<ColumnPropertyToUpdate> ).IsModified = true;
...
ctx.SaveChanges();
If you do this, EF will only include the columns you've marked as modified in the UPDATE statement.

Related

How to filter data in model in ActionResult of controller in ASP.net core MVC?

I have an index.chtml set up with about 10 ActionLinks. Those actionLinks trigger different ActionResult functions within the controller since each of them essentially perform unique queries on a data model.
I also have an entities object named db which has all the data. Instead of just displaying all the data, I want to perform complex filtering on the entities object to find where certain properties of records are null or where a property is greater than some input then returns a view of all columns on only those records that were filtered.
Find nulls:
public class printJobsController : Controller {
private PrintJobsEntities db = new PrintJobsEntities
public ActionResult IncompleteJobs {
//get jobs where processDate is null
...
}
}
Find where count is greater than 10:
public class printJobsController : Controller {
private PrintJobsEntities db = new PrintJobsEntities
public ActionResult JobsGreaterThan(int limit) {
//group by printerName and find counts greater than limit
...
}
}
How do I go about doing this?
Seems you are trying to populate the View with filtered data as per your request parameter in controller action.
You could follow the below steps to achieve what you are trying to:
Your imaginary Data Model
public class PrinterJob
{
[Key]
public int PrinterId { get; set; }
public string PrinterName { get; set; }
public int PrintedBy { get; set; }
public int TotalPrint { get; set; }
}
Sample Data In Database:
Controller Action:
public ActionResult <PrinterJob> JobsGreaterThan(int limit) {
var printCountByGroup =
(from objPrint in _context.PrinterJobs group objPrint by new {
objPrint.PrinterName, objPrint.PrintedBy, objPrint.TotalPrint
}
into grp where grp.Sum(p => p.TotalPrint) > limit
select new {
PrinterName = grp.Key.PrinterName, PrintedBy = grp.Key.PrintedBy,
TotalPrint = grp.Key.TotalPrint
});
return View(printCountByGroup);
}
Output After Applying Filter:
Note: Here I am trying to filter printer information which printed more then 30 printing jobs.
Hope it would help you to achieve your goal. If you still have any problem feel free to let me know.

Entity framework Core Raw SQLQueries with custom model

Using Entity Framework 6, I was able to use execute a Raw SQL Query and use a custom model which was not defined in the DBContext in order to store the output of the query. A simple example is the following:
List<MyModel> data = context.Database.SqlQuery<MyModel>("SELECT Orders.OrderID, Customers.CustomerName FROM Orders INNER JOIN Customers ON Orders.CustomerID=Customers.CustomerID;").ToList();
I execute one SQL command and I expect a list of custom models.
I try to do something similar with Entity Framework Core and the closest example that I found will force me to define a property from DBContext. This will not allow me to use a custom model to fill the data that SQL server will return.
var books = context.Books.FromSql("SELECT * FROM Books").ToList();
This query informs Entity Framework Core that the query will return a list of books. Is there a way to implement something like this in Entity Framework Core?
From .NET Core 2.1:
Add modelBuilder.Query<YourModel>() to OnModelCreating(ModelBuilder modelBuilder)
Use context.Query<YourModel>().FromSql(rawSql) to get data
Here's how I was able to get this working (for completeness):
MyModel.cs:
public class MyModel
{
// The columns your SQL will return
public double? A { get; set; }
public double? B { get; set; }
}
Add class that just inherits from your original EF context class (i called mine DbContextBase):
public class DbContext : DbContextBase
{
public virtual DbSet<MyModel> MyModels { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Necessary, since our model isnt a EF model
modelBuilder.Entity<MyModel>(entity =>
{
entity.HasNoKey();
});
}
}
Use that class (instead of your original EF context class):
// Use your new db subclass
using (var db = new DbContext())
{
var models = await db.MyModels.FromSqlRaw(...).ToListAsync(); // E.g.: "SELECT * FROM apple A JOIN banana B ON A.col = B.col"
}
Notes:
If you need to, just use FromSqlInterpolated instead of
FromSqlRaw
The "db context" subclass allows you to update EF models without affecting your "polyfill" code
Works with SQL Server stored procs that return only 1 result set
The question was about .NET Core 2. Now I have a solution and I am going to write it here so that someone else could use it in case he/she needs it.
First of all we add the following method in dbContext class
public List<T> ExecSQL<T>(string query)
{
using (var command = Database.GetDbConnection().CreateCommand())
{
command.CommandText = query;
command.CommandType = CommandType.Text;
Database.OpenConnection();
List<T> list = new List<T>();
using (var result = command.ExecuteReader())
{
T obj = default(T);
while (result.Read())
{
obj = Activator.CreateInstance<T>();
foreach (PropertyInfo prop in obj.GetType().GetProperties())
{
if (!object.Equals(result[prop.Name], DBNull.Value))
{
prop.SetValue(obj, result[prop.Name], null);
}
}
list.Add(obj);
}
}
Database.CloseConnection();
return list;
}
}
Now we can have the following code.
List<Customer> Customers = _context.ExecSQL<Customer>("SELECT ......");
follow these steps:
Create your model
Probably it could be better if you can reduce it to a model as generic as possible but it's not a must:
public class MyCustomModel
{
public string Text { get; set; }
public int Count { get; set; }
}
Add it to your own DbContext
Create DbSet for your custom model
public virtual DbSet<MyCustomModel> MyCustomModelName { get; set; }
Keep in mind to specify your custom model has no key
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
...
modelBuilder.Entity<MyCustomModel>().HasNoKey();
}
Use it from your dbContext instance
async public Task<List<MyCustomModel>> GetMyCustomData()
{
var rv = new List<MyCustomModel>();
using (var dataContext = new DbContext())
{
var sql = #"
select textField as 'Text', count(1) as 'Count'
from MyTable";
rv = await dataContext.Set<MyCustomModel>().FromSqlRaw(sql).ToListAsync();
}
return rv;
}

loading dropdown list with data from table in mvc4

I have 3 models and corresponding tables (EnquiryModel, EmployeeModel, RegionModel).
I want to populate a dropdown list in both the 'create' views of Enquiry and Employee.
That dropdown list should be filled with the data from the 'Region_Name' field in the RegionModel Table.
How can I do this?
I have RegionModel:
namespace MvcConQuery.Models
{
[Table("Region_Details")]
public class RegionModel
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public Int32 Region_id { get; set; }
public string Region { get; set; }
}
}
And also I have EmployeeModel and EnquiryModel, I give the following code in controller of these models:
public ActionResult Create()
{
IEnumerable<SelectListItem> abc = new SelectList(db.Regions, "Region_id", "Region");
ViewBag.regions = abc;
return View();
}
I give this code in Create View:
#Html.DropDownList("regions","select")
But I got the error:
There is no ViewData item of type 'IEnumerable' that has the key 'regions'.
Please help me to code the Create()function in controller and the Create View.
Try like this,
DATAEntities _context = new DATAEntities ();
var List = (from r in _context.Registrations select r).ToList();
ViewBag.CustomerName = new SelectList(List, "Region_id", "Region");
View
#Html.DropDownList("regions", (SelectList)ViewBag.CustomerName, "--Select--")
If you have any concerned let me know.

Initializing referenced objects in entity framework unit of work

I have a class in Entity framework 5 (using MVC 4):
public class JobFunction
{
public int Id { get; set; }
public string JobFunctionName { get; set; }
public int StatusId { get; set; }
public Status JFStatus { get; set; }
}
In my OnModelCreating method, I establish a FK relationship with the Status table as follows:
modelBuilder.Entity<JobFunction>().HasRequired(a => a.JFStatus).
WithMany().HasForeignKey(u => u.StatusId).WillCascadeOnDelete(false);
In my controller, I get a list of JobFunction objects as follows:
List<JobFunction> jfList = uow.JobFunctionRepository.GetAll().ToList<Catalog>();
where uow is my Unit of Work object, and JobFunctionRepository is defined. When I examine any JobFunction object in jfList, I see the following in my watch window:
Id: 1
JfStatus: null
JobFunctionName: "Manager"
StatusId: 2
Note that JFStatus is null. My question is: what provisions do I make in my code to initialize JFStatus to the appropriate Status object (based on the value of StatusId), during my GetAll call?
Thanks in advance.
-Zawar
You need some instrument to apply eager loading when you load the data through your repository. For example you could give your GetAll method a parameter list of expressions for the navigation properties you want to include in your query:
using System.Data.Entity;
//...
public IQueryable<JobFunction> GetAll(
params Expression<Func<JobFunction, object>>[] includes)
{
IQueryable<JobFunction> query = context.JobFunctions;
foreach (var include in includes)
query = query.Include(include);
return query;
}
Then you call it like so:
List<JobFunction> jfList = uow.JobFunctionRepository
.GetAll(jf => jf.JFStatus)
.ToList();
The JFStatus property should be filled now.

Supersedes clause in database structure

Imagine a database table that looks like this:
create table [dbo].[user]
(
id int IDENTITY(1,1),
username varchar(50) NOT NULL,
firstname varchar(20) NOT NULL,
lastname varchar(30) NOT NULL,
currentid int NULL,
processedby varchar(50) NOT NULL,
processeddate varchar(50) NOT NULL
processedaction varchar(50) NOT NULL
)
What I want to do is to setup NHibernate to load it into my user object, but I only want the current version of the object "user" to be brought back. I know how to do a SQL select to do this on my own, and I feel as if there's something in nHibernate with the usage of triggers and event listeners, but can anyone tell me how to implement the nHibernate repository so I can:
{Repository}.GetCurrent(id) <- pass it any of the ids that are assigned to any of the historical or the current record, and get back the current object.
{Repository}.Save(user) <- I want to always insert the changes to a new row, and then update the old versions to link back to the new id.
Edit
So, there's some confusion here, and maybe I explained it wrong... What I'm trying to do is this, in regards to always getting the current record back...
Select uc.*
FROM User uo
JOIN User uc on uo.currentid=uc.id
WHERE uo.id==:id
But, I don't want to expose "CurrentID" to my object model, since it has no bearing on the rest of the system, IMHO. In the above SQL statement, uo is considered the "original" object set, and uc is considered the current object in the system.
Edit #2:
Looking at this as a possible solution.
http://ayende.com/blog/4196/append-only-models-with-nhibernate
I'm honestly being pigheaded, as I'm thinking about this backward. In this way of running a database, the autoincrementing field should be the version field, and the "id" field should be whatever the autoincrementer's value has at the time of the initial insert.
Answer:
I don't want to take #Firo's fury, and I'm not going to remove it from him, as he took me down the right path... what I wound up with was:
Created a base generic class with two types given
a. type of the object's "ID"
b. type of the object itself.
instantiate all classes.
create a generic interface IRepository class with a type of the object to store/retrieve.
create an abstract generic class with a type of the object to store/retrieve.
create a concrete implementation class for each type to store/retrieve.
inside of the create/update, the procedure looks like:
Type Commit(Type item)
{
var clone = item.DeepClone();
_Session.Evict(item);
clone.Id = 0;
clone.ProcessedDate = DateTime.Now;
if (clone.Action.HasValue)
{
if (clone.Action == ProcessedAction.Create)
clone.Action = ProcessedAction.Update;
}
else
{
clone.Action = ProcessedAction.Create;
}
clone.ProcessedBy = UserRepos.Where(u => u.Username == System.Threading.Thread.CurrentPrincipal.Identity.Name).First().Current;
var savedItem = (_Session.Merge(clone) as Type);
_Session.CreateQuery("UPDATE Type SET CurrentID = :newID where ID=:newID OR CurrentID=:oldID")
.SetParameter("newID", savedItem.Id)
.SetParameter("oldID", item.Id)
.ExecuteUpdate();
return savedItem;
}
In the delete method, we simply update the {object}.Action = ProcessedAction.Delete
I wanted to do this another way, but realizing we need to eventually do historical comparisons, we weren't able to ask nHibernate to filter the deleted objects, as the users will want to see that. We'll create a business facade to take care of the deleted records.
Again, much thanks to #Firo for his help with this.
So, with all that, I can finally do this:
var result = {Repository}.Where(obj => obj.Id == {objectID from caller}).FirstOrDefault();
if (result != null)
{
return result.Current;
}
else
{
return null;
}
and always get my current object back for any requesting ID. Hope it helps someone that is in my situation.
in mapping if you use FluentNHibernate
public UserMap : ClassMap<User>
{
public UserMap()
{
Where("id = currentid"); // always bring back the most recent
}
}
// in Userrepository
public void Update(User user)
{
var clone = user.Clone();
session.Evict(user); // to prevent flushing the changes
var newId = session.Save(clone);
session.CreateQuery("UPDATE User u SET u.currentid = :current") // <-- hql
.SetParameter("current", newId)
.ExecuteUpdate();
}
objectgraphs are a lot trickier with this simple code. I would then do one of the following:
use NHibernate.Envers to store auditing information for me
explicitly creating new entities in BL code
i once saw an append-only-model doing something like the following
// UserBase is there to ensure that all others referencing the User doesnt have to update because user properties changed
class UserBase
{
public virtual int Id { get; set; }
public virtual ICollection<PersonDetails> AllDetails { get; private set; }
public virtual PersonDetails CurrentDetails
{
get { return _currentDetauils; }
set { _currentDetauils = value; AllDetails.Add(value); }
}
// same as above
public virtual ICollection<ConfigDetails> AllConfigs { get; set; }
}
class Order
{
public virtual int Id { get; set; }
public virtual UserBase User { get; set; }
public virtual IList<OrderDetail> AllDetails { get; private set; }
public virtual IList<OrderDetail> ActiveDetails { get; private set; }
public virtual void Add(OrderDetail detail)
{
AllDetails.Add(detail);
ActiveDetails.Add(detail);
}
public virtual void Delete(OrderDetail detail)
{
detail.Active = false;
ActiveDetails.Remove(detail);
}
}
class OrderDetail
{
public virtual int Id { get; set; }
public virtual Order Parent { get; set; }
public virtual bool Active { get; set; }
}
class OrderMap : ClassMap<Order>
{
public OrderMap()
{
HasMany(o => o.AllDetails);
HasMany(o => o.ActiveDetails).Where("active=1");
}
}
// somewhere
public void UpdateTaxCharge(OrderDetail detail, TaxCharge charge)
{
var clone = detail.Clone();
clone.TaxCharge = charge;
detail.Order.Delete(detail);
detail.Order.Add(clone);
}
You can tell NHibernate what exactly SQL it should generate when persisting and loading an entity. For example you can tell NHibernate to use a stored procedure instead of a plain SQL statement. If this is an option for you I can farther elaborate my answer.