Entity Framework related entities on object is not updated - c#

Whenever I try to change Department and Position objects of the Employee class they are not updating inside database on the Employee entity. For example, I want to change employee Position from junior (id - 1, name of the position - junior) to middle (id - 2, name of the position - middle). But, whenever I try to save this changes to the database they don't apply.
My Employee entity
public class Employee
{
public int EmployeeId { get; set; }
public string FullName { get; set; }
public Department Department { get; set; }
public EmployeePosition Position { get; set; }
public string Head { get; set; }
public double Salary { get; set; }
public Employee()
{
Department = new Department();
Position = new EmployeePosition();
}
}
EmployeePosition entity
public class EmployeePosition
{
public int Id { get; set; }
public string Name { get; set; }
}
My modify employee method in ViewModel
public void ModifyEmployee(int id)
{
Employee employee = employeeRepository.GetEmployee(id);
using (var context = new PowerCoEntity())
{
employee.Position = context.EmployeePositions.FirstOrDefault(d => d.Id == SelectedPositionId);
employee.Department = context.Deprtments.FirstOrDefault(d => d.DepartmentId == SelectedDepartmentId);
if (SelectedHeadId != null)
employee.Head = employeeRepository.GetHeadName(SelectedHeadId.Value);
else employee.Head = "";
}
employee.Salary = Employee.Salary;
employee.FullName = Employee.FullName;
employeeRepository.ModifyEmployee(employee);
}
Modify method in the context class
public void ModifyEmployee(Employee employee)
{
using (var context = new PowerCoEntity())
{
context.Entry(employee).State = EntityState.Modified;
context.SaveChanges();
}
}
UPDATE 1
I got it working by changing ModifyEmployee methods in the ViewModel and Context class. But I need to pass all the parameters through method now. How can I be able to use different contexts to modify object?
Context class:
public void ModifyEmployee(int id, int selectedPositionId, int selectedDepartmentId, int? selectedHeadId, Employee employee)
{
using (var context = new PowerCoEntity())
{
Employee emp = context.Employees.FirstOrDefault(e=> e.EmployeeId == 32);
emp.Salary = employee.Salary;
emp.FullName = employee.FullName;
emp.Department = employee.Department;
emp.Position = context.EmployeePositions.FirstOrDefault(d => d.Id == selectedPositionId);
emp.Department = context.Deprtments.FirstOrDefault(d => d.DepartmentId == selectedDepartmentId);
if (selectedHeadId != null)
emp.Head = GetHeadName(selectedHeadId.Value);
else emp.Head = "";
emp.Salary = employee.Salary;
emp.FullName = employee.FullName;
context.Entry(emp).State = EntityState.Modified;
context.SaveChanges();
}
}
ViewModel:
public void ModifyEmployee(int id)
{
Employee employee = new Employee();
employee.Salary = Employee.Salary;
employee.FullName = Employee.FullName;
employeeRepository.ModifyEmployee(id, SelectedPositionId, SelectedDepartmentId, SelectedHeadId, employee);
}

Related

ASP.NET MVC View is showing NULL

I have a Controller that I have hard coded some values and expect the values to populate the 2 models with the data. However, there is one Model that is null when I run through the controller. Here are some snippets of the app.
When I run the code, Orders is null. I would expect there be 3 entries in customer orders but that is null.
CustomerOrderModel.cs:
public class CustomerOrderModel
{
public int OrderId { get; set; }
public DateTime OrderDate { get; set; }
public string Description { get; set; }
public decimal Total { get; set; }
}
CustomerViewModel.cs:
public class CustomerViewModel
{
public int CustomerNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
get { return FirstName + " " + LastName; }
}
public List<CustomerOrderModel> Orders { get; set; }
}
Controller.cs:
public IActionResult Orders()
{
decimal iTotal = 55.23M;
CustomerViewModel customer = new CustomerViewModel();
List<CustomerOrderModel> orders = new List<CustomerOrderModel>();
try
{
for (int i = 1; i < 4; i++)
{
var order = new CustomerOrderModel();
customer.CustomerNumber = 111111;
customer.FirstName = "John";
customer.LastName = "Smith";
order.OrderId = i;
order.OrderDate = DateTime.Now;
order.Description = "Part " + i;
order.Total = iTotal;
iTotal += order.Total;
orders.Add(order);
}
return View(customer);
}
catch (Exception)
{
return View("Error");
}
}
I have tried to re-code the IActionResult Orders() without any luck. customer.Orders gives me no members.
Re-code Orders
public IActionResult Orders1()
{
decimal iTotal = 55.23M;
List<CustomerOrderModel> orders = new List<CustomerOrderModel>();
for (int i = 1; i < 4; i++)
{
CustomerViewModel customer = new CustomerViewModel();
customer.Orders = new List<CustomerOrderModel>();
customer.CustomerNumber = 111111;
customer.FirstName = "John";
customer.LastName = "Smith";
}
return View();
}
In the public IActionResult Orders() action method set the Orders property value before render the view:
customer.Orders = orders;
return View(customer);
The reference type default value is null and this what you see.
Default values of C# types (C# reference)

Model one to many in RavenDb for better performance

I'm approaching to document database and I'm little bit confused how to map documents relationship, in a situation as follow
public class Person
{
public Person()
{
}
public int Id { get; set; }
public string Name { get;set;}
public string Surname { get; set; }
public DateTime? BirthDate { get; set; }
}
public class Car
{
public Car() { }
public int Id { get; set; }
public string Name { get; set; }
public int PersonId { get; set;}
}
A person has one or more cars for example in this way I can query the db as follow
public Car Get(int id)
{
Car car = null;
using (IDocumentSession session = store.OpenSession())
{
car = session.Include<Car, Person>(x => x.PersonId).Load<Car>(id);
bool isLoaded = session.Advanced.IsLoaded("people/" + car.PersonId); // true!
}
return car;
}
and it's everything ok, the client makes just one request, but if I have a person and I want to show all his cars how can I query the db to do just a request?
I think tha I must modify the model putting a List<int> Cars in Person for reference his cars.
Note that I don't want to embed Cars in the Person document because Cars can be referenced from others document.
Thanks.
You can do it like this:
using (IDocumentSession session = store.OpenSession())
{
var carsForOne = session.Query<Car>()
.Include(x=>x.PersonId)
.Where(x=>x.PersonId == "people/1")
.ToList();
var person = session.Load<Person>("people/1");
}
This make just a single db request.
You can index the Cars collection and load all the cars from the index.
The index would look like this:
public class CarIndex : AbstractIndexCreationTask<Car, CarView>
{
public CarIndex()
{
Map = cars => from car in cars
select new
{
car.Id,
car.Name,
car.PersonId,
};
}
}
The CarView class is identical to the Car class, but can be changed to better fit the indexing needs.
public class CarView
{
public int Id { get; set; }
public string Name { get; set; }
public int PersonId { get; set; }
}
You'll need to execute the index before being able to use it:
new CarIndex().Execute(store);
Loading the cars for a certain person would look like this:
using (IDocumentSession session = store.OpenSession())
{
session.Store(new Person { Id = 1, Name = "A", Surname = "A" });
session.Store(new Car { Id = 1, Name = "A", PersonId = 1 });
session.Store(new Car { Id = 2, Name = "B", PersonId = 1 });
session.Store(new Car { Id = 3, Name = "C", PersonId = 2 });
session.SaveChanges();
}
WaitForIndexing(store); // from RavenTestBase
using (IDocumentSession session = store.OpenSession())
{
var resultsForId1 = session
.Query<CarView, CarIndex>()
.ProjectFromIndexFieldsInto<CarView>()
.Where(x => x.PersonId == 1);
Assert.Equal(2, resultsForId1.Count());
var resultsForId2 = session
.Query<CarView, CarIndex>()
.ProjectFromIndexFieldsInto<CarView>()
.Where(x => x.PersonId == 2);
Assert.Equal(1, resultsForId2.Count());
}
If you want to load the person and their cars in a single database request, use lazy loading:
var resultsForId1 = session
.Query<CarView, CarIndex>()
.ProjectFromIndexFieldsInto<CarView>()
.Where(x => x.PersonId == 1).Lazily();
var person = session.Advanced.Lazily.Load<Person>(1);
var personValue = person.Value;
var resultsValue = resultsForId1.Value;
Complete test (needs xunit and RavenDB.Tests.Helpers nugets):
using Raven.Client;
using Raven.Client.Indexes;
using Raven.Tests.Helpers;
using System;
using System.Linq;
using Xunit;
namespace SO41547501Answer
{
public class SO41547501 : RavenTestBase
{
[Fact]
public void SO41547501Test()
{
using (var server = GetNewServer())
using (var store = NewRemoteDocumentStore(ravenDbServer: server))
{
new CarIndex().Execute(store);
using (IDocumentSession session = store.OpenSession())
{
session.Store(new Person { Id = 1, Name = "A", Surname = "A" });
session.Store(new Car { Id = 1, Name = "A", PersonId = 1 });
session.Store(new Car { Id = 2, Name = "B", PersonId = 1 });
session.Store(new Car { Id = 3, Name = "C", PersonId = 2 });
session.SaveChanges();
}
WaitForAllRequestsToComplete(server);
WaitForIndexing(store);
using (IDocumentSession session = store.OpenSession())
{
var resultsForId1 = session
.Query<CarView, CarIndex>()
.ProjectFromIndexFieldsInto<CarView>()
.Where(x => x.PersonId == 1);
Assert.Equal(2, resultsForId1.Count());
var resultsForId2 = session
.Query<CarView, CarIndex>()
.ProjectFromIndexFieldsInto<CarView>()
.Where(x => x.PersonId == 2);
Assert.Equal(1, resultsForId2.Count());
}
using (IDocumentSession session = store.OpenSession())
{
server.Server.ResetNumberOfRequests();
var resultsForId1 = session
.Query<CarView, CarIndex>()
.ProjectFromIndexFieldsInto<CarView>()
.Where(x => x.PersonId == 1).Lazily();
var person = session.Advanced.Lazily.Load<Person>(1);
var personValue = person.Value;
var resultsValue = resultsForId1.Value;
Assert.Equal("A", personValue.Name); // person data loaded
Assert.Equal("A", resultsValue.First().Name); // cars data loaded
Assert.Equal(1, server.Server.NumberOfRequests); // only one request sent to the server
}
}
}
}
public class CarIndex : AbstractIndexCreationTask<Car, CarView>
{
public CarIndex()
{
Map = cars => from car in cars
select new
{
car.Id,
car.Name,
car.PersonId,
};
}
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public DateTime? BirthDate { get; set; }
}
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public int PersonId { get; set; }
}
public class CarView
{
public int Id { get; set; }
public string Name { get; set; }
public int PersonId { get; set; }
}
}

Unable to set field/property CartItems on entity type BusinessEntitys.Product

I spent many hours (including wide Google search) trying to figure out what the problem is. Unfortunately, without success.
The error that I get is: "Unable to set field/property CartItems on entity type BusinessEntitys.Product. See InnerException for details."
the innerExeption is null.
The error occurs at addToCart() function when I try to call a function addCartItem through the WCF. When the call get to DAL and try to save the new object in db context it's failed.
public void AddToCart(int id)
{
// Retrieve the product from the database.
ShoppingCartId = GetCartId();
CartItem cartItem = new BLFrontend().getAllCartItems().SingleOrDefault(
c => c.CartItemId == ShoppingCartId
&& c.ProductId == id);
if (cartItem == null)
{
Product temp = new BLFrontend().getAllProducts().SingleOrDefault(
p => p.ProductId == id);
// Create a new cart item if no cart item exists.
cartItem = new CartItem
{
CartItemId = Guid.NewGuid().ToString(),
ProductId = id,
CartId_ = ShoppingCartId,
Product = temp,
Quantity = 1,
};
new BLFrontend().addCartItem(cartItem);//The problem starts from here
}
else
{
cartItem.Quantity++;
//update
}
I'm using EF in my project, this is the Model:
namespace BusinessEntitys
{
using System;
using System.Collections.Generic;
public partial class CartItem
{
public string CartItemId { get; set; }
public string CartId_ { get; set; }
public int Quantity { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
}
class Product that contained in CratItem:
namespace BusinessEntitys
{
using System;
using System.Collections.Generic;
using System.Linq; // added this
public partial class Product
{
public Product()
{
this.ItemInOrders = new HashSet<ItemInOrder>();
this.CartItems = new HashSet<CartItem>();
}
public int ProductId { get; set; }
public string ProductName { get; set; }
public double ProductBasePrice { get; set; }
public double ProductDiscount { get; set; }
public string ProductDescription { get; set; }
public string ProductImageURL { get; set; }
public string ProductQualityLevel { get; set; }
public string ProductCategory { get; set; }
public virtual ICollection<CartItem> CartItems { get; set; }
}
}
My BusinessEntitys is:
namespace BusinessEntitys
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
public partial class StoreDBEntities : DbContext
{
public StoreDBEntities()
: base("name=StoreDBEntities")
{
base.Configuration.ProxyCreationEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Client> Clients { get; set; }
public virtual DbSet<ItemInOrder> ItemInOrders { get; set; }
public virtual DbSet<Order> Orders { get; set; }
public virtual DbSet<Product> Products { get; set; }
public virtual DbSet<StockManagment> StockManagments { get; set; }
public virtual DbSet<CartItem> CartItems { get; set; }
}
}
Execption occurs here in my DAL at 2nd line when trying to execute the command:
context.CartItems.Add(CartItemToAdd)
public bool addCartItem(CartItem CartItemToAdd)
{
try
{
var context = new StoreDBEntities();
context.CartItems.Add(CartItemToAdd);
return context.SaveChanges() > 0;
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
The stackTracer:
StackTrace "at DataAccess.Dal.addCartItem(CartItem CartItemToAdd) in
c:\Users\User\Documents\Visual Studio
2013\Projects\OrGarden\DataAccess\Dal.cs:line 406\r\n at
BusinessLogicBackend.BLBackend.addCartItem(CartItem cartItemToAdd) in
c:\Users\User\Documents\Visual Studio
2013\Projects\OrGarden\BusinessLogicBackend\BLBackend.cs:line
293\r\n at SyncInvokeaddCartItem(Object , Object[] , Object[] )\r\n
at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object
instance, Object[] inputs, Object[]& outputs)\r\n at
System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc&
rpc)" string
Important Update: When i deleted this assignment Product = temp, the addCartItem call succeed saved without a problem. Only that the field Product remains null.
The code after that change:
Product temp = business.getAllProducts().SingleOrDefault(
p => p.ProductId == id);
// Create a new cart item if no cart item exists.
cartItem = new CartItem
{
CartItemId = Guid.NewGuid().ToString(),
ProductId = id,
CartId_ = ShoppingCartId,
Product = temp,
Quantity = 1,
};
business.addCartItem(cartItem);
Whene i retrieved CrartItem from the context the field Product remains null.
Can anyone help me fix that bad problem??
Thank you very much,
Ron
First of all, I strongly recommend you take a look at the Unit of Work pattern (MSDN version, if you prefer).
The problem is, you're creating a new instance of BLFrontEnd and StoreDBEntities every time. It seems that there's no reason to create the instances so.
I'm not sure what exactly going on inside the BLFrontEnd, but I bet every methods on the BLFrontEnd also create a new StoreDBEntities instance. And that might be the cause of the exception.
You need to manage the life cycle of instances. look at this:
public void AddToCart(int id)
{
// Begins new life-cycle of BLFrontend
var frontend = new BLFrontend();
// or you can wrap it using statement.
// using(var frontend = new BLFrontend())
// {
// ... code ...
// }
// Retrieve the product from the database.
ShoppingCartId = GetCartId();
CartItem cartItem = frontend.getAllCartItems().SingleOrDefault(
c => c.CartItemId == ShoppingCartId
&& c.ProductId == id);
if (cartItem == null)
{
Product temp = frontend.getAllProducts().SingleOrDefault(
p => p.ProductId == id);
// Create a new cart item if no cart item exists.
cartItem = new CartItem
{
CartItemId = Guid.NewGuid().ToString(),
ProductId = id,
CartId_ = ShoppingCartId,
Product = temp,
Quantity = 1,
};
frontend.addCartItem(cartItem);//The problem starts from here
}
else
{
cartItem.Quantity++;
//update
}
// end of life-cycle of the instance.
frontend.Dispose();
}
public class BLFrontEnd : IDispose
{
private readonly StoreDBEntities dbContext;
public BLFrontEnd()
{
dbContext = new StoreDBEntities();
}
public void Dispose()
{
dbContext.Dispose();
}
public IQueryable<Cart> getAllCartItems()
{
// your logics...
return dbContext.Carts.AsQueryable();
}
public IQueryable<Product> getAllProducts()
{
// your logics...
return dbContext.Products.AsQueryable();
}
public bool addCartItem(CartItem CartItemToAdd)
{
try
{
//var context = new StoreDBEntities();
dbContext.CartItems.Add(CartItemToAdd);
return context.SaveChanges() > 0;
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
}
As you can see, 'A work' is beginning at the head of AddToCart() method, and ended at the tail of the method. That is the life-cycle of 'A work'. During the life-cycle, every resources are in the same context. That means every code in the life-cycle will use same instance of resources. It avoids unnecessary creation of new instance.
This code isn't that good, and you need to refactor in order to meet your use-cases and the architecture. But I hope you got the point.
Edited
Okay, to separate actual problem with your BL, could you try this:
public void AddToCart(int id)
{
// use StoreDBEntities directly, instead of BLFrontend.
using(var dbContext = new StoreDBEntities())
{
// Retrieve the product from the database.
ShoppingCartId = 123; // use magic number for this test.
// and use dbContext directly.
CartItem cartItem = dbContext.Carts.SingleOrDefault(
c => c.CartItemId == ShoppingCartId
&& c.ProductId == id);
if (cartItem == null)
{
Product temp = dbContext.Products.SingleOrDefault(
p => p.ProductId == id);
// Create a new cart item if no cart item exists.
cartItem = new CartItem
{
CartItemId = Guid.NewGuid().ToString(),
ProductId = id,
CartId_ = ShoppingCartId,
Product = temp,
Quantity = 1,
};
dbContext.Carts.Add(cartItem);
}
else
{
cartItem.Quantity++;
//update
}
dbContext.SaveChanges();
}
}

Nested LINQ query selection

Please consider following example class construct:
public class Company
{
public string CompanyName { get; set; }
public List<Subdivision> Subdivisions { get; set; }
}
public class Subdivision
{
public string SubdivisionName { get; set; }
public List<Employee> Employees { get; set; }
}
public class Employee
{
public int EmployeeID { get; set; }
public string EmployeeName { get; set; }
}
Example variable of a List:
List<Company> CompanyList = new List<Company>();
CompanyList.Add(new Company
{
CompanyName = "TestCompany",
Subdivisions = new List<Subdivision>
{
{ new Subdivision
{
SubdivisionName = "TestSubdivision",
Employees = new List<Employee>
{
{ new Employee
{
EmployeeID = 1,
EmployeeName = "John"
}
}
}
}}
}
});
I want to get the EmployeeName just by EmployeeID. Consider this code:
if (CompanyList.Any(x => x.Subdivisions.Any(y => y.Employees.Any(z => z.EmployeeID == 1))))
{
int i1 = CompanyList.IndexOf(CompanyList.Where(x => x.Subdivisions.Any(y => y.Employees.Any(z => z.EmployeeID == 1))).Select(x => x).First());
int i2 = CompanyList[i1].Subdivisions.IndexOf(CompanyList[i1].Subdivisions.Where(x => x.Employees.Any(z => z.EmployeeID == 1)).Select(x => x).First());
int i3 = CompanyList[i1].Subdivisions[i2].Employees.IndexOf(CompanyList[i1].Subdivisions[i2].Employees.Where(z => z.EmployeeID == 1).Select(x => x).First());
string i = CompanyList[i1].Subdivisions[i2].Employees[i3].EmployeeName;
Console.WriteLine(i);
}
else
{
Console.WriteLine("Employee with ID 1 not found!");
}
This works just fine; however, it seems rather bloated up if I just want to retrieve a piece of data without getting the indexes. Is there any other approach to this?
You can use SelectMany to search for all employees in all divisions in all companies and then use FirstOrDefault to make sure null would be returned if no employee would be found
var employee = CompanyList.SelectMany(company => company.Subdivisions.SelectMany(division => division.Employees))
.FirstOrDefault(emp => emp.EmployeeID == 1);
if (employee != null)
{
Console.WriteLine(employee.EmployeeName); //prints John
}
else
{
Console.WriteLine("Employee with ID 1 not found!");
}

How should I prevent multiple inserts here?

I am inserting 5000 transactions with about 6 banks, but I get 5000 bank rows in the database with duplicate names.
public class Transaction
{
[Key]
public int Id { get; set; }
public DateTime TransDate { get; set; }
public decimal Value { get; set; }
public string Description { get; set; }
public Bank Bank { get; set; }
}
public class Bank
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class FinancialRecordContext : DbContext
{
public FinancialRecordContext() : base("FinancialRecordDatabase") { }
public DbSet<Transaction> Transactions { get; set; }
public DbSet<Bank> Banks { get; set; }
public Bank FindOrInsertBank(string bankName)
{
var bank = Banks.SingleOrDefault(b => b.Name == bankName);
if (bank == null)
{
bank = new Bank { Name = bankName };
Banks.Add(bank);
}
return bank;
}
}
Then to insert I am looping through some data and inserting thusly:
using (var context = new FinancialRecordContext())
{
foreach (var t in data)
{
var tran = new Transaction
{
Description = t.Description,
Value = t.Value,
TransDate = t.TransDate,
Bank = context.FindOrInsertBank(t.BankName)
};
context.Transactions.Add(tran);
}
context.SaveChanges();
}
It would appear that the FindOrInsertBank method is going to the database all the time and not looking locally at it's recently added, but not committed banks. How can/should I be doing this?
Should I SaveChanges after each bank insert? Not really what I want to do I want this to be all one transaction.
A couple of suggestions to try:
1) Check the Local collection of DbSet<Bank> first.
public Bank FindOrInsertBank(string bankName)
{
var bank = Banks.Local.SingleOrDefault(b => b.Name == bankName);
if (bank == null)
{
var bank = Banks.SingleOrDefault(b => b.Name == bankName);
if (bank == null)
{
bank = new Bank { Name = bankName };
Banks.Add(bank);
}
}
return bank;
}
2) Force a call to DetectChanges() after each update
context.Transactions.Add(tran);
context.ChangeTracker.DetectChanges();
You can try to query the change tracker (which is an in-memory query, not a database query):
using (var context = new FinancialRecordContext())
{
foreach (var t in data)
{
Bank bank = context.ChangeTracker.Entries()
.Where(e => e.Entity is Bank && e.State != EntityState.Deleted)
.Select(e => e.Entity as Bank)
.SingleOrDefault(b => b.Name == t.BankName);
if (bank == null)
bank = context.FindOrInsertBank(t.BankName);
var tran = new Transaction
{
Description = t.Description,
Value = t.Value,
TransDate = t.TransDate,
Bank = bank
};
context.Transactions.Add(tran);
}
context.SaveChanges();
}
Edit
Using the change tracker here could be bad for performance because Bank.Name is not the key of the entity and I guess the query would be a linear search through the entries. In this case using a handwritten dictionary might be the better solution:
using (var context = new FinancialRecordContext())
{
var dict = new Dictionary<string, Bank>();
foreach (var t in data)
{
Bank bank;
if (!dict.TryGetValue(t.BankName, out bank))
{
bank = context.FindOrInsertBank(t.BankName);
dict.Add(t.BankName, bank);
}
var tran = new Transaction
{
Description = t.Description,
Value = t.Value,
TransDate = t.TransDate,
Bank = bank
};
context.Transactions.Add(tran);
}
context.SaveChanges();
}

Categories