Good evening.
I am building a small web API for project management.
Each Project is split into many Tasks.
Eventually I attempt to access the Project object(master) from the Task object(slave) but the GET request(on a Task) gets no response via postman or an an incomplete response via chrome, just like this:
GET /api/task/1 :
{
"id": 1,
"name": "Task A",
"description": "Primary task",
"priority": 0,
"state": 0,
"projectId": 1,
"project": {
"id": 1,
"name": "Project 1",
"description": "First project",
"deadline": "0001-01-01T00:00:00",
"clientId": 0,
"tasks": [
I'll present the model, context and controller classes and I wish to get your assistance on how to correctly indicate to EF to include the Project object corresponding to the foreign key field in the Task.
Here are the class for the Project model:
//Project.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ProjectManagementAPI.Models
{
public class Project
{
public Project()
{
Tasks = new List<Task>();
}
public int Id { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public DateTime Deadline { get; set; }
public int ClientId { get; set; }
public ICollection<Task> Tasks { get; set; }
}
}
And for Task:
//Task.cs
namespace ProjectManagementAPI.Models
{
public enum STATE { Pending, Started, Blocked, Finished }
public class Task
{
public Task()
{
}
public int Id { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public int Priority { get; set; }
public STATE State { get; set; }
[ForeignKey("ProjectId")]
public int ProjectId { get; set; }
public Project Project { get; set; }
}
}
The Context class details the relationship via Fluent API:
//ProjectManagementDBEntities.cs
namespace ProjectManagementAPI.Models
{
public class ProjectManagementDBEntities : DbContext
{
public ProjectManagementDBEntities(DbContextOptions<ProjectManagementDBEntities> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Task>()
.HasOne(p => p.Project)
.WithMany(b => b.Tasks)
.OnDelete(DeleteBehavior.Cascade);
}
public DbSet<Project> Projects { get; set; }
public DbSet<Task> Tasks { get; set; }
}
}
Project controller class (update and delete controls omitted):
//ProjectController.cs
namespace ProjectManagementAPI.Controllers
{
[Route("api/[controller]")]
public class ProjectController : Controller
{
private readonly ProjectManagementDBEntities _context;
public ProjectController(ProjectManagementDBEntities context)
{
_context = context;
}
[HttpGet]
public IEnumerable<Project> GetAll()
{
return _context.Projects.ToList();
}
[HttpGet("{id}", Name = "GetProject")]
public IActionResult GetById(long id)
{
var project = _context.Projects.FirstOrDefault(t => t.Id == id);
if (project == null)
{
return NotFound();
}
return new ObjectResult(project);
}
[HttpPost]
public IActionResult Create([FromBody] Project project)
{
if (project == null)
{
return BadRequest();
}
project.Tasks = new List<Task>();
_context.Projects.Add(project);
_context.SaveChanges();
return CreatedAtRoute("GetProject", new { id = project.Id }, project);
}
}
In the Task controller, I use LINQ's Include() method to request eager loading of the Task object. I suspect that it's where the mistake resides.
//TaskController.cs
namespace ProjectManagementAPI.Controllers
{
[Route("api/[controller]")]
public class TaskController : Controller
{
private readonly ProjectManagementDBEntities _context;
public TaskController(ProjectManagementDBEntities context)
{
_context = context;
}
[HttpGet]
public IEnumerable<Task> GetAll()
{
return _context.Tasks.Include(t => t.Project).ToList();
}
[HttpGet("{id}", Name = "GetTask")]
public IActionResult GetById(long id)
{
var task = _context.Tasks.Include(t => t.Project).FirstOrDefault(t => t.Id == id);
if (task == null)
{
return NotFound();
}
return new ObjectResult(task);
}
[HttpPost]
public IActionResult Create([FromBody] Task Task)
{
if (Task == null)
{
return BadRequest();
}
_context.Tasks.Add(Task);
_context.SaveChanges();
return CreatedAtRoute("GetTask", new { id = Task.Id }, Task);
}
}
Thank you.
Related
My problem relates to sales orders and sales invoices but I find it easier to think of pets and their offspring... without creating a full pedigree model.
My DbContext
using System;
using DevExpress.ExpressApp.EFCore.Updating;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using DevExpress.Persistent.BaseImpl.EF.PermissionPolicy;
using DevExpress.Persistent.BaseImpl.EF;
using DevExpress.ExpressApp.Design;
using DevExpress.ExpressApp.EFCore.DesignTime;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using DevExpress.ExpressApp.DC;
using System.Collections.Generic;
namespace Pets.Module.BusinessObjects
{
[TypesInfoInitializer(typeof(PetsContextInitializer))]
public class PetsEFCoreDbContext : DbContext
{
public PetsEFCoreDbContext(DbContextOptions<PetsEFCoreDbContext> options) : base(options)
{
}
public DbSet<Cat> Cats { get; set; }
public DbSet<Dog> Dogs { get; set; }
public DbSet<Kitten> Kittens { get; set; }
public DbSet<Puppy> Puppys { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Pet>()
.HasDiscriminator(x=> x.IsCat)
.HasValue<Cat>(true)
.HasValue<Dog>(false);
modelBuilder.Entity<BabyPet>()
.HasDiscriminator(x => x.IsCat)
.HasValue<Kitten>(true)
.HasValue<Puppy>(false);
modelBuilder.Entity<Puppy>().HasOne(x => x.Parent).WithMany(x => x.Puppies);
modelBuilder.Entity<Kitten>().HasOne(x => x.Parent).WithMany(x => x.Kittens);
}
}
}
My classes
public abstract class Pet
{
[Key] public int Id { get; set; }
public string Name { get; set; }
public bool? IsCat { get; set; }
}
public abstract class BabyPet
{
[Key] public int Id { get; set; }
public int ParentPetId { get; set; }
[ForeignKey("ParentPetId")]
public virtual Pet Parent { get; set; }
public string Name { get; set; }
public bool? IsCat { get; set; }
}
public class Kitten : BabyPet
{
new public virtual Cat Parent { get; set; }
}
public class Dog : Pet
{
public Dog()
{
Puppies = new List<Puppy>();
}
[Aggregated]
public virtual List<Puppy> Puppies { get; set; }
}
public class Cat : Pet
{
public Cat()
{
Kittens = new List<Kitten>();
}
[Aggregated]
public virtual List<Kitten> Kittens { get; set; }
}
public class Puppy : BabyPet
{
new public virtual Dog Parent { get; set; }
}
Also there is
public class PetsContextInitializer : DbContextTypesInfoInitializerBase
{
protected override DbContext CreateDbContext()
{
var optionsBuilder = new DbContextOptionsBuilder<PetsEFCoreDbContext>()
.UseSqlServer(#";");
return new PetsEFCoreDbContext(optionsBuilder.Options);
}
}
However this creates the following structure in BabyPet
Where as I just want
[Update]
I was able to get the structure I want by specifying the foreignkey in OnModelCreating
modelBuilder.Entity<Puppy>().HasOne(x => x.Parent).WithMany(x => x.Puppies).HasForeignKey(x=>x.ParentPetId);
modelBuilder.Entity<Kitten>().HasOne(x => x.Parent).WithMany(x => x.Kittens).HasForeignKey(x => x.ParentPetId);
However when I try to add a Kitten to a cat via the XAF Winforms UI I get:
Unable to cast object of type 'SimplePets.Module.BusinessObjects.Kitten' to type 'SimplePets.Module.BusinessObjects.Puppy'.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.get_Item(IPropertyBase propertyBase)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.GetCurrentValue(IPropertyBase propertyBase)
at DevExpress.EntityFrameworkCore.Security.NetStandard.ChangeTracking.SecurityStateManager.TryAddPropertyNameToCollection(InternalEntityEntry entity, ICollection`1 propertiesToCheck, IPropertyBase property)
at DevExpress.EntityFrameworkCore.Security.NetStandard.ChangeTracking.SecurityStateManager.TryAddPropertyNameToCollection(InternalEntityEntry entity, IProperty property, ICollection`1 propertiesToCheck)
at DevExpress.EntityFrameworkCore.Security.NetStandard.ChangeTracking.SecurityStateManager.GetPropertiesToCheck(InternalEntityEntry entity)
at DevExpress.EntityFrameworkCore.Security.NetStandard.ChangeTracking.SecurityStateManager.CheckReadWritePermissionsForNonIntermediateObject(InternalEntityEntry entity)
at DevExpress.EntityFrameworkCore.Security.NetStandard.ChangeTracking.SecurityStateManager.CheckReadWritePermissions(InternalEntityEntry entity)
at DevExpress.EntityFrameworkCore.Security.NetStandard.ChangeTracking.SecurityStateManager.CheckIsGrantedToSave(InternalEntityEntry entity)
at DevExpress.EntityFrameworkCore.Security.NetStandard.ChangeTracking.SecurityStateManager.GetEntriesToSave(Boolean cascadeChanges)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(DbContext _, Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
at DevExpress.ExpressApp.EFCore.EFCoreObjectSpace.DoCommit()
at DevExpress.ExpressApp.BaseObjectSpace.CommitChanges()
at DevExpress.ExpressApp.Win.SystemModule.WinModificationsController.Save(SimpleActionExecuteEventArgs args)
at DevExpress.ExpressApp.SystemModule.ModificationsController.saveAction_OnExecute(Object sender, SimpleActionExecuteEventArgs e)
at DevExpress.ExpressApp.Actions.SimpleAction.RaiseExecute(ActionBaseEventArgs eventArgs)
at DevExpress.ExpressApp.Actions.ActionBase.ExecuteCore(Delegate handler, ActionBaseEventArgs eventArgs)
I put my example on GitHub here
Docs link about relationships here and tph inheritance is here
I think I must have the data structures correct after my update to onModelCreating. That is :
modelBuilder.Entity<Puppy>().HasOne(x => x.Parent).WithMany(x => x.Puppies).HasForeignKey(x=>x.ParentPetId);
modelBuilder.Entity<Kitten>().HasOne(x => x.Parent).WithMany(x => x.Kittens).HasForeignKey(x => x.ParentPetId);
I was able to work around the Cast Object error by using DBContext instead of ObjectSpace
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Actions;
using SimplePets.Module.BusinessObjects;
using System.Linq;
namespace SimplePets.Module.Win.Controllers
{
public class KittenViewController : ViewController
{
SimpleAction actionAddKittenEF;
SimpleAction actAddKittenXAF;
public KittenViewController() : base()
{
TargetObjectType = typeof(Kitten);
TargetViewNesting = Nesting.Nested;
actAddKittenXAF = new SimpleAction(this, "Add via OS", "View");
actAddKittenXAF.Execute += actAddKittenXAF_Execute;
actionAddKittenEF = new SimpleAction(this, "Add via Db", "View");
actionAddKittenEF.Execute += actionAddKittenEF_Execute;
}
private void actionAddKittenEF_Execute(object sender, SimpleActionExecuteEventArgs e)
{
var cat = View.ObjectSpace.GetObject(((NestedFrame)Frame).ViewItem.CurrentObject) as Cat;
var db = Helpers.MakeDb();
var kitten = new Kitten
{
Parent = db.Cats.FirstOrDefault(c => c.Id == cat.Id),
Name = $"baby {cat.Kittens.Count + 1} of {cat.Name}"
};
db.Kittens.Add(kitten);
db.SaveChanges();
View.ObjectSpace.Refresh();
}
//Errors
private void actAddKittenXAF_Execute(object sender, SimpleActionExecuteEventArgs e)
{
var cat = View.ObjectSpace.GetObject(((NestedFrame)Frame).ViewItem.CurrentObject) as Cat;
var os = View.ObjectSpace;
var kitten = os.CreateObject<Kitten>();
kitten.Parent = cat;
kitten.Name = $"baby {cat.Kittens.Count + 1} of {cat.Name}";
View.ObjectSpace.CommitChanges();
View.ObjectSpace.Refresh();
}
}
}
Is it not possible to have a second reference to second class? FirstClass contains SecondClasses and SeocondBegin containing the begin element. With this code I get the execption in SaveChanges:
System.InvalidOperationException: 'Unable to save changes because a circular dependency was detected in the data to be saved: 'FirstClass { 'Id': -2147482647 } [Added] <-
SecondClasses FirstClass { 'FirstClassId': -2147482647 } SecondClass { 'Id': -2147482647 } [Added] <-
SecondBegin { 'SecondBeginId': -2147482647 } FirstClass { 'Id': -2147482647 } [Added]'.'
I would like the have this property because the second class should be a 'linked list' and the collection SecondClasses does not containing the
The source is:
namespace EFTestApp
{
public class FirstClass
{
public int Id { get; set; }
public int? SecondBeginId { get; set; }
public string Name { get; set; }
[ForeignKey(nameof(SecondBeginId))]
public SecondClass SecondBegin { get; set; }
[InverseProperty(nameof(EFTestApp.SecondClass.FirstClass))]
[IgnoreDataMember]
public ICollection<SecondClass> SecondClasses { get; set; }
}
}
namespace EFTestApp
{
public class SecondClass
{
public int Id { get; set; }
public int FirstClassId { get; set; }
public string Url { get; set; }
[ForeignKey(nameof(FirstClassId))]
public FirstClass FirstClass { get; set; }
public SecondClass Next { get; set; }
}
}
namespace EFTestApp
{
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.EnableSensitiveDataLogging();
optionsBuilder.UseSqlServer(#"Server=(localdb)\MSSQLLocalDB;Database=sample;Trusted_Connection=True");
}
public DbSet<FirstClass> FirstClasses { get; set; }
public DbSet<SecondClass> SecondClasses { get; set; }
}
}
namespace EFTestApp
{
class Program
{
static void Main(string[] args)
{
var dbContext = new ApplicationDbContext();
dbContext.Database.EnsureCreated();
var firstClass = new FirstClass()
{
Name = "First"
};
var secondClass = new SecondClass()
{
FirstClass = firstClass,
Url = "Blablah"
};
firstClass.SecondBegin = secondClass;
dbContext.Add(firstClass);
dbContext.SaveChanges();
}
}
}
It's fine to have a cycle, but you can't create it with a single call to SaveChanges() as EF isn't able to INSERT either row without the other.
You'll have to call SaveChanges twice here, once to insert the entities, and then again to "close" the cycle.
Eg
dbContext.Add(firstClass);
dbContext.Add(secondClass);
dbContext.SaveChanges();
firstClass.SecondBegin = secondClass;
dbContext.SaveChanges();
Hi I am trying to build angular 2 web application using WebAPI, Entityframework that is loosely coupled using dependency injection. I am using unity for dependency injection. I have created multiple projects in one solution to address the separation concerns.
I have configured the dependency in unity.config however when i execute the webapi application and type the following url http://localhost:8702/api/allcustomers , I get message saying the customer controller doesn't have parameter-less constructor. I have set my break points in unity.config which never get hit
I would like to to understand if my implementation is correct as well
Below is the structure of my solution
CustomerOrder.Business.Objects
CustomerOrder.Data.Objects (references the business object)
CustomerOrder.Service.Api (references business object and service implementation)
CustomerOrder.Service.Implementation (references business objects and data objects)
CustomerOrder.Web (Yet to implement)
Below is the code
CustomerOrder.Business.Objects
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
public string Email { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public int? Zip { get; set; }
}
CustomerOrder.Data.Objects
public class CustomerDao : ICustomerDao
{
IEnumerable<CustomerOrder.BusinessObjects.Customer> ICustomerDao.GetAllCustomers()
{
using (var customerOrderContext = new Entities())
{
return (from customer in customerOrderContext.Customers
select new CustomerOrder.BusinessObjects.Customer
{
Id = customer.Id,
FirstName = customer.FirstName,
LastName = customer.LastName,
Address = customer.Address,
City = customer.City,
Email = customer.Email,
Gender = customer.Gender,
State = customer.State,
Zip = customer.Zip
}).ToList();
}
}
}
public interface ICustomerDao
{
/// <summary>
/// Get All Customers
/// </summary>
/// <returns></returns>
IEnumerable<Customer> GetAllCustomers();
}
public interface IDaoFactory
{
ICustomerDao CustomerDao { get; }
}
}
public class DaoFactory : IDaoFactory
{
public DaoFactory(ICustomerDao CustomerDao, IProductDao ProductDao, IOrderDao OrderDao)
{
this.CustomerDao = CustomerDao;
}
public ICustomerDao CustomerDao { set; get; }
}
CustomerOrder.Service.Api
Unity.Config
public static void RegisterComponents()
{
var container = new UnityContainer();
// register all your components with the container here
// it is NOT necessary to register your controllers
// e.g. container.RegisterType<ITestService, TestService>();
container.RegisterType<ICustomerProvider, CustomerProvider>();
container.RegisterType<IOrderProvider, OrderProvider>();
container.RegisterType<IProductProvider, ProductProvider>();
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
}
CustomerController.cs
public class CustomerController : ApiController
{
private ICustomerProvider customerProvider;
public CustomerController(ICustomerProvider customerProvider)
{
this.customerProvider = customerProvider;
}
[Route("api/allcustomers")]
public IEnumerable<Customer> GetAllCustomers()
{
return customerProvider.GetAllCustomers();
}
CustomerOrder.Service.Implementation
public interface ICustomerProvider
{
IEnumerable<BusinessObjects.Customer> GetAllCustomers();
}
public class CustomerProvider : ICustomerProvider
{
private readonly IDaoFactory dataAccess;
public CustomerProvider(IDaoFactory dalFactory)
{
this.dataAccess = dalFactory;
}
public IEnumerable<BusinessObjects.Customer> GetAllCustomers()
{
IList<BusinessObjects.Customer> customerCollection = new List<BusinessObjects.Customer>();
dataAccess.CustomerDao.GetAllCustomers();
return customerCollection;
}
}
Context Class
namespace CustomerOrderData.EF
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
public partial class Entities : DbContext
{
public Entities()
: base("name=Entities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Customer> Customers { get; set; }
public virtual DbSet<OrderDetail> OrderDetails { get; set; }
public virtual DbSet<Order> Orders { get; set; }
public virtual DbSet<Product> Products { get; set; }
}
}
In CustomerProvider, the IDaoFactory is probably not getting resolved because it's not registered. Add this to the Unity.Config:
container.RegisterType<IDaoFactory , DaoFactory >();
Please try including a parameterless constructor into the customer controller.
public CustomerController() {}
You should register not only IDaoFactory and his constructor dependencies
container.RegisterType<IDaoFactory, DaoFactory>();
container.RegisterType<ICustomerDao, CustomerDao>();
container.RegisterType<IOrderDao, OrderDao>();
container.RegisterType<IProductDao, ProductDao>();
I am building a WebAPI and I have populated my Database with 5 different Organizations, each organization has 3 different Contacts, each Contact has 3 phone numbers with EntityFramework.
But somehow it behaves in a weird way:
I get a complete list of Contacts and organizations
I get just a contact under an organization (GET -> api/organizations)
I get just one phone under a contact (GET -> api/contacts)
It does not accept parameter in the api routes such as: api/contacts/{id} or api/organizations{id}. It returns 404 code.
My questions:
1- Are the relation between models is wrong built and therefore I get just one sub-element instead the whole list?
2- Why does my routes do not accept parameters {id}?
These are my models:
**Organization:**
public class Organization {
public Guid Id { get; set; }
public DateTime dateCreated { get; set; }
public string organizationName { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
}
**Contact:**
public class Contact {
public Contact() { }
public Guid Id { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
public virtual ICollection<Phone> Phones { get; set; }
// Foreign Key for Organizations
public Guid OrganizationId { get; set; }
//Related Organization entity
[ForeignKey("OrganizationId")]
public Organization OrganizationData { get; set; }
}
**Phones:**
public class Phone {
public Guid Id { get; set; }
public string customName { get; set; }
public string phoneNumber { get; set; }
// Foreign Key for Contacts
public Guid ContactId { get; set; }
//Related Contact entity
[ForeignKey("ContactId")]
public Contact ContactData { get; set; }
}
These are my controllers:
**OrganizationsController:**
[Route("api/organizations")]
public class OrganizationsController : Controller
{
[HttpGet ("")]
public IActionResult Get()
{
var results = _repository.GetAllOrganizations();
return Ok(Mapper.Map<IEnumerable<Organization>>(results));
}
[HttpGet("")]
public IActionResult Get(Guid Id)
{
var organization = _repository.GetOrganizationById(Id);
return Ok(organization);
}
}
**ContactsController**
[Route("api/organization/{id}/contacts")]
public class ContactsController : Controller
{
[HttpGet("")]
public IActionResult Get(Guid Id)
{
var organization = _repository.GetOrganizationById(Id);
return Ok(organization.Contacts.ToList());
}
}
}
**AllContactsController**
[Route("api/contacts")]
public class AllController : Controller
{
[HttpGet("")]
public IActionResult Get()
{
var results = _repository.GetAllContacts();
return Ok(Mapper.Map<IEnumerable<Contact>>(results));
}
[HttpGet("/{id}")]
public IActionResult Get(Guid Id)
{
var contact = _repository.GetContactById(Id);
return Ok(contact);
}
EDIT: adding the repository:
public class ContactManagementRepository : IContactManagementRepository
{
private ContactManagementContext _context;
private ILogger<ContactManagementRepository> _logger;
public ContactManagementRepository(ContactManagementContext context, ILogger<ContactManagementRepository> logger)
{
_context = context;
_logger = logger;
}
public IEnumerable<Organization> GetAllOrganizations()
{
_logger.LogInformation("Getting All Organizations from the Database");
return _context.Organizations.ToList();
}
public IEnumerable<Contact> GetAllContacts()
{
_logger.LogInformation("Getting All Contacts from the Database");
return _context.Contacts.ToList();
}
public void AddOrganization(Organization organization)
{
_context.Add(organization);
}
public void AddContact(Guid id, Contact newContact)
{
var organization = GetOrganizationById(id);
if(organization != null)
{
organization.Contacts.Add(newContact);
_context.Contacts.Add(newContact);
}
}
public async Task<bool> SaveChangesAsync()
{
return (await _context.SaveChangesAsync()) > 0;
}
public Organization GetOrganizationById(Guid Id)
{
return _context.Organizations
.Include(c => c.Contacts)
.Where(c => c.Id == Id)
.FirstOrDefault();
}
public Contact GetContactById(Guid Id)
{
return _context.Contacts
.Include(c => c.Addresses)
.Include(c => c.Bankdatas)
.Include(c => c.Phones)
.Where(c => c.Id == Id)
.FirstOrDefault();
}
public void DeleteContact(Guid id)
{
var contact = GetContactById(id);
if (contact != null)
{
_context.Contacts.Remove(contact);
}
}
}
Try for OrganizationsController
[HttpGet("{id}")]
public IActionResult Get(Guid Id)
{
var organization = _repository.GetOrganizationById(Id);
return Ok(organization);
}
and try for AllController
[HttpGet("{id}")]
public IActionResult Get(Guid Id)
{
var contact = _repository.GetContactById(Id);
return Ok(contact);
}
Your models look good for me. Can you show your repositories?
I'm trying to find an easy solution for updating an entity + the included properties in my solution. I've created an Generic Repository for my DBContext (database). It does update the parent entity, but not handling changes on the child properties. Is there a way to handle or track those changes?
Example code for updating child propery: (look at comment - example code)
[HttpPut]
public HttpResponseMessage PutBrand(Brand brand)
{
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
try
{
// example code
brand.BrandSizes.FirstOrDefault().Name = "I'm a Test";
// add values
brand.State = State.Changed;
brand.DateChanged = DateTime.Now;
// update
brand = _brandService.UpdateBrand(brand);
// save
_brandService.SaveBrandChanges();
// signalR
Hub.Clients.All.UpdateBrand(brand);
return Request.CreateResponse<Brand>(HttpStatusCode.OK, brand);
}
catch (Exception ex)
{
return Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message);
}
}
Context:
public class ERPContext : DbContext
{
#region Catalog
public DbSet<Brand> Brands { get; set; }
public DbSet<BrandSize> BrandSizes { get; set; }
public DbSet<BrandSizeOption> BrandSizeOptions { get; set; }
public DbSet<BrandTierPrice> BrandTierPrices { get; set; }
#endregion Catalog
public ERPContext()
: base("db-erp")
{
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
Generic Repository:
public class ERPRepository<T> : IRepository<T> where T : class
{
#region Fields
private DbSet<T> _dbSet;
private DbContext _dataContext;
#endregion Fields
#region Ctor
public ERPRepository(DbContext dataContext)
{
if (dataContext == null)
{
throw new ArgumentNullException("dataContext", "dataContext cannot be null");
}
_dataContext = dataContext;
_dbSet = _dataContext.Set<T>();
}
#endregion Ctor
#region Methods
public T Add(T item)
{
return _dbSet.Add(item);
}
public T Delete(T item)
{
return _dbSet.Remove(item);
}
public T Update(T item)
{
var updated = _dbSet.Attach(item);
_dataContext.Entry(item).State = EntityState.Modified;
return updated;
}
public IQueryable<T> Query(params Expression<Func<T, object>>[] includes)
{
var query = _dbSet;
if (includes != null)
{
includes.ToList().ForEach(x => query.Include(x).Load());
}
return query;
}
public void SaveChanges()
{
_dataContext.SaveChanges();
}
#endregion Methods
}
Model:
public class Brand
{
#region Ctr
public Brand()
{
BrandSizes = new List<BrandSize>();
BrandTierPrices = new List<BrandTierPrice>();
}
#endregion Ctr
#region Properties
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int? LogoId { get; set; }
public int DisplayOrder { get; set; }
public bool Deleted { get; set; }
public bool Locked { get; set; }
public State State { get; set; }
public DateTime DateChanged { get; set; }
public DateTime DateCreated { get; set; }
#endregion Properties
#region Mapping
public virtual Picture Logo { get; set; }
public virtual List<BrandSize> BrandSizes { get; set; }
public virtual List<BrandTierPrice> BrandTierPrices { get; set; }
#endregion Mapping
}
BrandService:
public partial class BrandService : IBrandService
{
#region Fields
private readonly IRepository<Brand> _brandRepository;
private readonly IRepository<BrandSize> _brandSizeRepository;
private readonly IRepository<BrandSizeOption> _brandSizeOptionRepository;
#endregion Fields
#region Ctor
public BrandService(IRepository<Brand> brandRepository, IRepository<BrandSize> brandSizeRepository, IRepository<BrandSizeOption> brandSizeOptionRepository)
{
_brandRepository = brandRepository;
_brandSizeRepository = brandSizeRepository;
_brandSizeOptionRepository = brandSizeOptionRepository;
}
#endregion Ctor
#region Methods
public virtual IEnumerable<Brand> GetAllBrands()
{
return _brandRepository.Query(x => x.BrandSizes);
//return _brandRepository.Query();
}
public virtual Brand GetBrandById(int id)
{
return _brandRepository.Query().Where(x => x.Id == id).FirstOrDefault();
}
public virtual Brand InsertBrand(Brand brand)
{
return _brandRepository.Add(brand);
}
public virtual Brand UpdateBrand(Brand brand)
{
return _brandRepository.Update(brand);
}
public virtual Brand DeleteBrand(Brand brand)
{
return _brandRepository.Delete(brand);
}
public virtual void SaveBrandChanges()
{
_brandRepository.SaveChanges();
}
#endregion Methods
}
Create IObjectWithState interface and State enum to track changes manually:
public interface IObjectWithState
{
State State { get; set; }
}
public enum State
{
Added,
Unchanged,
Modified,
Deleted
}
and implement the interface in every mapped entity
public class Brand:IObjectWithState
{ ....
[NotMapped]
public State State { get; set; }}
and add these two helper methods to convert the state and to apply the changes in the entire graph:
public static EntityState ConvertState(State state)
{
switch (state)
{
case State.Added :
return EntityState.Added;
case State.Deleted:
return EntityState.Deleted;
case State.Modified:
return EntityState.Modified;
case State.Unchanged:
return EntityState.Unchanged;
default:
return EntityState.Unchanged;
}
}
public static void ApplyStateChanges(this DbContext context)
{
foreach (var entry in context.ChangeTracker.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = StateHelpers.ConvertState(stateInfo.State);
}
}
and when update or insert any object edit the state of it like this object.State = State.Modified;
and then modify your insert or update method to be like this:
public void InsertOrUpdate(T entity, bool IsGraph)
{
if (((IObjectWithState)entity).State == State.Added)
{
dataContext.Entry(entity).State = System.Data.Entity.EntityState.Added;
}
else
{
dbset.Add(entity);
dataContext.Entry(entity).State = System.Data.Entity.EntityState.Modified;
}
//This method change the state of every changed object
if (IsGraph)
ApplyStateChanges(dataContext);
dataContext.Commit();
}