I built a very simple MVC3 application to do a little demo, but I'm running against a problem; I return an entity to my view, edit it and then post it back, but in this process my entity loses it's change-tracking capabilities. When I return the entity to my view it's still an entity framework proxy class, but when it comes back from my view it's a 'Person' class (the entity is called person).
Here's my repository class:
public class PersonRepository : IPersonRepository
{
public EfContext Uow { get; set; }
public PersonRepository(IUnitOfWork uow)
{
Uow = uow as EfContext;
}
// yada yada yada
public void Add(Person person)
{
Uow.Persons.Add(person);
}
}
This entity is sent to my view that has a simple form, created with Html.EditorForModel. After that I post it back to this method:
[HttpPost]
public ActionResult Edit(Person person)
{
if (ModelState.IsValid)
{
_personRepository.Add(person);
_personRepository.Uow.Commit();
return RedirectToAction("Index");
}
return View(person);
}
And tada, it's no longer a tracked proxy class. This results in a primary key violation because entity framework is trying to add my object as a new object, where I just want entity framework to detect the changes and create an update statement instead. Oh by the way, the Commit method in the code above just calls SaveChanges(), here's the class:
public class EfContext : DbContext, IUnitOfWork
{
public DbSet<Account> Accounts { get; set; }
public DbSet<Person> Persons { get; set; }
public void Commit()
{
SaveChanges();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
By the way, this is my entity class:
public class Person
{
[HiddenInput(DisplayValue = false)]
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Account> Accounts { get; set; }
}
Does anyone know how to fix this? I had this working before as far as I can remember, I just don't know how.
Thanks in advance!
I assume that you're creating your repository per request (in the constructor or passed in as a dependency by your IoC framework or something). In that case, the entity that you receive in your controller method is indeed not tracked, because it was created in a different request. You have to "attach" it to the context and mark it as modified so EF knows it has changed and it needs to be saved to the DB
Define an Update method in your repository that will attach the entity and mark it as modified.
public class PersonRepository : IPersonRepository
{
// yada yada yada
public void Add(Person person)
{
Uow.Persons.Add(person);
}
public void Update(Person person)
{
Uow.Persons.Attach(person);
Uow.Entry(person).State = EntityState.Modified;
}
}
[HttpPost]
public ActionResult Edit(Person person)
{
if (ModelState.IsValid)
{
_personRepository.Update(person);
_personRepository.Uow.Commit();
return RedirectToAction("Index");
}
return View(person);
}
There is a 3rd solution here, which I believe is the most failsafe one.
You can configure your MVC action to receive an id integer. Then, using that integer, you retrieve the entity from the database. Then you call the UpdateModel method on the controller. This will bind the new form values to your existing object while it doesn't change the unchanged properties and properties that may not have been in the form.
Related
I have to build a .net web application accessing tables of an existing db.
The db uses different tables for different companies: customers in company "ACorp" are stored in table "ACorpCustomers", those in company "B" are stored in table "BCorpCustomers".
Using ADO .NET Entity Model, I created a different Db Context for each Company:
public partial class ACorpContext : DbContext
{
public ACorpContext()
: base("name=ACorpContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<ACorpCustomer> ACorpCustomers { get; set; }
}
}
The edmx generates also the class
public partial class ACorpCustomer
{
public string Name { get; set; }
public string Phone { get; set; }
}
I created a parent class Customer to be used in the application, with the same properties:
public class ACorpCustomer
{
public virtual string Name { get; set; }
public virtual string Phone { get; set; }
}
I havent't found a way to let the specific entity ACorpCustomers inherit from the parent Customer; the edmx returns the inheritance error, but there is no way to override the properties.
Update
In order to avoid edmx file usage, this is what I finally tried out:
I disabled the __MigrationHistory sql table creation using the AutomaticMigrationsEnabled parameter:
internal sealed class Configuration : DbMigrationsConfiguration<MyDomain.Models.ACorpContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
}
I disabled the db initialization in the App.config file setting
disableDatabaseInitialization="true"
Then I added a an ADO .NET Entity Model but chose the "code first from database".
In order to be sure not to change the db from the model, I disabled the DB Initializer:
public ACorpContext()
: base("name=ACorpContext")
{
Database.SetInitializer<ACorpContext>(null);
}
Now I expect to be my responsability to be keep in sync the domain model with the db.
Anyway, I feel sure that in case of misalignment no attempt will be done to modify the db.
Without the edmx, I have no more limitations defining inheritance from an abstract class Customer.
I cannot understand why Visual Studio considers this as "Code First" approach, anyway.
Your definition
public partial class ACorpCustomer
has nothing to do with inheritance. partial is a .NET moderator that signifies that your class definition is a part of the bigger definition. For example if you have your class split between 2 code files. .Net "puts" them together and you endup with one type
Here what you seem need to do is
public abstract class Customer
{
public string Name { get; set; }
public string Phone { get; set; }
}
public class ACorpCustomer : Customer
{
// may be, some unique properties here
}
public class BCorpCustomer : Customer
{
// may be, some unique properties here
}
The properties Name and Phone don't even need to be virtual. Looking back into your title, there is nothing that you need to override. Nothing that I see..
This is trivial in Code-First, which you can (and should) use with an existing database. Just map the single Customer entity to the correct table for each DbContext:
public partial class ACorpContext : MyBaseDbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().ToTable("ACorpContext");
}
public virtual DbSet<Customer> Customers{ get; set; }
}
Let us assume there are two assemblies - Core and Infrastructure. In the first one there are interfaces and models. It represents the domain model and contains business logic. Models are built on abstractions - just interface methods are used.
For instance:
public interface INotificationService
{
void Notify(User user, int modelId);
}
public interface IAuthorizationService
{
void IsAuthorized(User user);
}
public interface IPersistenceService
{
int AddSomeEntity(SomeModel model);
void SaveChanges();
}
public interface ISomeModelManagementService
{
void AddSomeModelAndNotify(User user, SomeModel model);
}
public class SomeModelManagementService : ISomeModelManagementService
{
//Here would be constructor injection of INotificationService, IAuthorizationService and IPersistenceService
void AddSomeModelAndNotify(User user, SomeModel model)
{
if(authorizationService.IsAuthorized(user))
{
int id = persistenceService.AddSomeEntity(model);
persistenceService.SaveChanges();
notificationService.Notify(user,id);
}
else
{
throw new UnauthorizedException();
}
}
}
All of the code above would reside in the Core assembly and have no reference to the implementation of the interfaces (except the ISomeModelManagementService).
In the Infrastructure assembly on the other hand the implementation of infrastructure dependent concerns would reside. There will be for example code responsible for sending e-mails by EMailNotificationService or mapping and persisting Core models in a database by DbPersistenceService.
The problem I'm facing is with this method:
int AddSomeEntity(SomeModel model);
I want to achieve batch SaveChanges and not to implement saving entities right after each command (insert/update/delete queries). The problem is with inserting new entities with Entity Framework. EF will update its model and return primary key after SaveChanges method is executed on the DbContext.
I've tried to pass parameter by reference, but it's not updated after SaveChanges.
It's possible to do this with dynamic type returned instead of int, so insert commands / add methods would return database model (not core model) and then there's possibility to get id of it after SaveChanges execution, but it's a dirty solution.
Is it possible for a reference to Entity Framework key to be returned by a method?
Is there any possibility to update returned id after SaveChanges execution?
int id = persistenceService.AddSomeEntity(model);
persistenceService.SaveChanges();
//Now I'd like to have id updated with the database key value
One solution could be to return a wrapper for the id. Since the wrapper is a reference type it should update Id after SaveChanges().
// Interface for entities that only contains id.
public interface IIdentifiableEntity
{
int Id { get; }
}
public interface INotificationService
{
void Notify(User user, IIdentifiableEntity entity);
}
public interface IAuthorizationService
{
void IsAuthorized(User user);
}
public interface IPersistenceService
{
// Return the entity as a interface here...
IIdentifiableEntity AddSomeEntity(SomeModel model);
void SaveChanges();
}
public interface ISomeModelManagementService
{
void AddSomeModelAndNotify(User user, SomeModel model);
}
public class SomeModelManagementService : ISomeModelManagementService
{
//Here would be constructor injection of INotificationService, IAuthorizationService and IPersistenceService
void AddSomeModelAndNotify(User user, SomeModel model)
{
if(authorizationService.IsAuthorized(user))
{
IIdentifiableEntity entity = persistenceService.AddSomeEntity(model);
persistenceService.SaveChanges();
// Pass reference entity to Notify.
notificationService.Notify(user,entity);
}
else
{
throw new UnauthorizedException();
}
}
}
Then your entities would have to implement the interface IIdentifiableEntity. I think that could be a more elegant solution than using dynamic. The positive thing is that it's a very small interface and it doesn't really expose the entity that much (except for the Id). The downside is that your entity leaves the scope of IPersistenceService, which may not be what you want.
A sample implementation would be:
public class PersistentCustomerService : IPersistenceService
{
public IIdentifiableEntity AddSomeEntity(Customer model)
{
var result = _context.Customers.Add(model);
return result;
}
}
public class Customer : IIdentifiableEntity
{
public int Id {get;set;}
public string Name {get;set;}
}
I'm trying to implement a business layer into my application. The reason for this is that my (legacy) database is very complex for the use cases we have. So what I'm trying to do is the following
Retrieve datamodel from the DbContext
Transform the datamodel to a business model
Pass it on to my controller to be used.
This works perfectly for retrieving objects, but updating them keeps giving me problems. Let me first give you (some of) my code (somewhat simplified):
using System;
/* The datamodel*/
public class DataModel
{
[Key]
public int Id { get; set; }
public double InterestRate { get; set; }
}
/*The business model */
public class BusinessModel
{
public int Id { get; set; }
public double InterestRate { get; set; }
public bool IsHighInterest()
{
return InterestRate > 10;
}
}
public class MyDbContext : DbContext
{
public MyDbContext() : base("connectionstring")
{
}
public DbSet<DataModel> DataModels { get; set; }
}
/* In reality I've got a repository here with a unit-of-work object instead of accessing the DbContext directly. */
public class BusinessLayer
{
public BusinessModel Get(int id)
{
using (var context = new MyDbContext())
{
var dataModel = context.DataModels.FirstOrDefault(x => x.Id == id);
BusinessModel = Transform(dataModel); //Do a transformation here
}
}
public void Update(BusinessModel model)
{
using (var context = new MyDbContext())
{
var dataModel = TransformBack(dataModel);
context.Entry<dataModel>.State = System.Data.Entity.EntityState.Modified;
context.SaveChanges();
}
}
}
Obviously this isn't going to work, because entity framework cannot track the changes of the datamodel anymore. I'm looking for a design pattern where I can do these sort of things. Hope anyone of you can help me with this. In reality the datamodel is way more complex and the BusinessModel simplyfies it a lot, so just using the DataModel isn't really an option either.
That's essentially the ViewModel pattern. While you can certainly add a repository keep in mind entity framework already implements Unit of Work, but I digress. Many of us do something very similar to your code using POCO entity models to interact with the database and then transforming those to ViewModels, DTOs, or as you call them Business Models. Automapper is great for this.
So in my update code I do something like this (MVC):
if (ModelState.IsValid)
{
var entity = context.Entities.First(e => e.Id == viewmodel.Id); // fetch the entity
Mapper.Map(viewmodel, entity); // Use automapper to replace changed data
context.SaveChanges();
}
If you have access to Pluralsight here is a good video on the topic: https://wildermuth.com/2015/07/22/Mapping_Between_Entities_and_View_Models
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();
}
}
I have my Database Context:
public class ProductContext : DbContext
{
public ProductContext() : base ("DefaultConnection") {}
public DbSet<Product> Products {get;set;}
}
and my Repository:
public class ProductRepository : IProductRepository
{
private ProductContext _dbContext = new ProductContext();
public IQueryable<Product> Products { get { return _dbContext.Products; } }
}
when I query my database in the Edit Action:
public ActionResult Edit(Guid id)
{
var item = _repository.Products.FirstOrDefault(x => x.Id.Equals(id));
return View(item);
}
I would usually use a ViewModel but this is purely to show the scenario.
When I query the database using the var item line, does EntityFramework change the state of that item.
Can I pass around that item through a multitude of Services in the Service Layer and then finally save it using my method:
public void SaveEntity<TEntity>(TEntity entityToSave) where TEntity : DbEntity
{
if (entityToSave.Id.Equals(Guid.Empty))
_dbContext.Set<TEntity>().Add(entityToSave);
else
_dbContext.Entry<TEntity>(entityToSave).State = EntityState.Modified;
_dbContext.SaveChanges();
}
It won't throw an exception saying that there is already a Entity with the same Id as the one you're trying to Save?
So after trial and error, it seems that this works perfectly fine, and it doesn't bring back any errors. There is one thing to look out for:
This navigation property:
public virtual Category Category { get;set; }
public Guid CategoryId { get;set; }
That could reside in the Product model has a little gotcha, that is:
When editing or saving a new Product, you should only set the CategoryId and not just the Category exclusively because you will get duplicate Category entries every time you edit or save if you use the a Category that already exist within the database...
I think you should the navigation property solely for your ease, not for use when modifying entities...