I analysed my code with NDepend and it detected some critical rules one of them is: "Avoid namespaces mutually dependent".
But I can't figure how I can organize my code to get rid of this "errors", in the case of navigation properties.
Of course I could place all my entities in the same namespace, but I think my actual "by namespace" class separation organization is not so bad.
Maybe I have to ignore this message and exclude my persistence assembly of the ndepend scan.
What am I missing?
first class:
using System;
using System.Collections.Generic;
using Persistence.Entities.ContactModule;
namespace Persistence.Entities.TeachingModule
{
public class Course
{
private Course() { }
public Course(string reference,Company company )
{
this.Reference = reference ?? throw new ArgumentNullException(nameof(reference));
this.Company = company ?? throw new ArgumentNullException(nameof(company));
}
public int Id { get; private set; }
public string Reference { get; private set; } = null!;
public int CompanyId { get; set; }
public Company Company { get; set; } = null!;
}
}
Second class :
using System;
using System.Collections.Generic;
using Persistence.Entities.TeachingModule;
namespace Persistence.Entities.ContactModule
{
public class Company
{
private Company() { }
public Company(string reference, string name)
{
this.Reference = reference ?? throw new ArgumentNullException(nameof(reference));
this.Name = name ?? throw new ArgumentNullException(nameof(name));
}
public int Id { get; private set; }
public string Name { get; private set; } = null!;
public ICollection<Course> Courses { get; set; } = new List<Course>();
}
}
Related
I try to create the following database design with EF Core (code-first)
Entity "Recipe" can have a list of type "Resource"
Entity "Shop" can have a single "Resource"
Entity "InstructionStep" can have a list of type "Resource"
If I delete a resource from the "Recipe", "InstructionStep" (collections) or from the "Shop" (single-property) then the corresponding "Resource" entity should be also deleted. (Cascade Delete)
I already tried several things with and without mapping tables but none of my approach was successful.
Another idea was to have a property "ItemRefId" in the "Resource" entity to save the "RecipeId/ShopId/InstructionStepId" but I don't get it to work...
Example Classes:
public class Recipe
{
public int RecipeId { get; set; }
public string Title { get; set; }
public ICollection<RecipeResource> Resources { get; set; } = new List<RecipeResource>();
}
public class Shop
{
public int ShopId { get; set; }
public string Title { get; set; }
public Resource Logo { get; set; }
}
public class Resource
{
public int ResourceId { get; set; }
public string Path { get; set; }
public int ItemRefId { get; set; }
}
public class InstructionStep
{
public string InstructionStepId { get; set; }
public string Title { get; set; }
public ICollection<RecipeResource> Resources { get; set; } = new List<RecipeResource>();
}
Any suggestions? Many thanks in advance.
That's not cascade delete. Cascade delete would be when a Recipe is deleted, all of the related Resources are deleted as well.
In EF Core 3, you can use Owned Entity Types for this. The generated relational model is different from what you are proposing, in that Recipe_Resource and InstructionStep_Resource will be seperate tables, and Shop.Logo will be stored in columns on the Shop table. But that's the correct relational model. Having one Resource table with some rows referencing a Recipe and some rows referencing an InstructionStep is a bad idea.
This scenario is sometimes called a "Strong Relationship" where the identity of the related entity is dependent on the main entity, and should be implemented in the relational model by having the the Foreign Key columns be Primary Key columns on the dependent entity. That way there's no way remove a Recipe_Resource without deleting it.
eg
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
namespace EfCore3Test
{
public class Recipe
{
public int RecipeId { get; set; }
public string Title { get; set; }
public ICollection<Resource> Resources { get; } = new List<Resource>();
}
public class Shop
{
public int ShopId { get; set; }
public string Title { get; set; }
public Resource Logo { get; set; }
}
public class Resource
{
public int ResourceId { get; set; }
public string Path { get; set; }
public int ItemRefId { get; set; }
}
public class InstructionStep
{
public string InstructionStepId { get; set; }
public string Title { get; set; }
public ICollection<Resource> Resources { get; } = new List<Resource>();
}
public class Db : DbContext
{
public DbSet<Recipe> Recipes { get; set; }
public virtual DbSet<Shop> Shops { get; set; }
public virtual DbSet<InstructionStep> InstructionSteps { get; set; }
private static readonly ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddFilter((category, level) =>
category == DbLoggerCategory.Database.Command.Name
&& level == LogLevel.Information).AddConsole();
});
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLoggerFactory(loggerFactory)
.UseSqlServer("Server=.;database=EfCore3Test;Integrated Security=true",
o => o.UseRelationalNulls());
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Shop>().OwnsOne(p => p.Logo);
modelBuilder.Entity<InstructionStep>().OwnsMany(p => p.Resources);
modelBuilder.Entity<Recipe>().OwnsMany(p => p.Resources);
}
}
class Program
{
static void Main(string[] args)
{
using var db = new Db();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
var r = new Recipe();
r.Resources.Add(new Resource() { ItemRefId = 2, Path = "/" });
db.Recipes.Add(r);
db.SaveChanges();
r.Resources.Remove(r.Resources.First());
db.SaveChanges();
var s = new Shop();
s.Logo = new Resource { ItemRefId = 2, Path = "/" };
db.Shops.Add(s);
db.SaveChanges();
s.Logo = null;
db.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 want to remove a row in database and insert it again with the same Id, It sounds ridiculous, but here is the scenario:
The domain classes are as follows:
public class SomeClass
{
public int SomeClassId { get; set; }
public string Name { get; set; }
public virtual Behavior Behavior { get; set; }
}
public abstract class Behavior
{
public int BehaviorId { get; set; }
}
public class BehaviorA : Behavior
{
public string BehaviorASpecific { get; set; }
}
public class BehaviorB : Behavior
{
public string BehaviorBSpecific { get; set; }
}
The entity context is
public class TestContext : DbContext
{
public DbSet<SomeClass> SomeClasses { get; set; }
public DbSet<Behavior> Behaviors { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<SomeClass>()
.HasOptional(s => s.Behavior)
.WithRequired()
.WillCascadeOnDelete(true);
}
}
Now this code can be executed to demonstrate the point
(described with comments in the code below)
using(TestContext db = new TestContext())
{
var someClass = new SomeClass() { Name = "A" };
someClass.Behavior = new BehaviorA() { BehaviorASpecific = "Behavior A" };
db.SomeClasses.Add(someClass);
// Here I have two classes with the state of added which make sense
var modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// They save with no problem
db.SaveChanges();
// Now I want to change the behavior and it causes entity to try to remove the behavior and add it again
someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" };
// Here it can be seen that we have a behavior A with the state of deleted and
// behavior B with the state of added
modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// But in reality when entity sends the query to the database it replaces the
// remove and insert with an update query (this can be seen in the SQL Profiler)
// which causes the discrimenator to remain the same where it should change.
db.SaveChanges();
}
How to change this entity behavior so that delete and insert happens instead of the update?
A possible solution is to make the changes in 2 different steps: before someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" }; insert
someClass.Behaviour = null;
db.SaveChanges();
The behaviour is related to the database model. BehaviourA and B in EF are related to the same EntityRecordInfo and has the same EntitySet (Behaviors).
You have the same behaviour also if you create 2 different DbSets on the context because the DB model remains the same.
EDIT
Another way to achieve a similar result of 1-1 relationship is using ComplexType. They works also with inheritance.
Here an example
public class TestContext : DbContext
{
public TestContext(DbConnection connection) : base(connection, true) { }
public DbSet<Friend> Friends { get; set; }
public DbSet<LessThanFriend> LessThanFriends { get; set; }
}
public class Friend
{
public Friend()
{Address = new FullAddress();}
public int Id { get; set; }
public string Name { get; set; }
public FullAddress Address { get; set; }
}
public class LessThanFriend
{
public LessThanFriend()
{Address = new CityAddress();}
public int Id { get; set; }
public string Name { get; set; }
public CityAddress Address { get; set; }
}
[ComplexType]
public class CityAddress
{
public string Cap { get; set; }
public string City { get; set; }
}
[ComplexType]
public class FullAddress : CityAddress
{
public string Street { get; set; }
}
I have build a .Net Mvc 4 application and now I want to extend it with REST.
I am using the Entity Framework and I have the following problem.
My goal is to have a system where categories have a number of products and where products can belong to multiple categories.
As follows:
public class Categorie
{
[Key]
public int Id { get; set; }
[Required]
public string Naam { get; set; }
[Required]
public string Omschrijving { get; set; }
public byte[] Plaatje { get; set; }
private List<Product> producten;
public virtual List<Product> Producten
{
get { return producten; }
set { producten = value; }
}
}
public class Product
{
[Key]
public int Id { get; set; }
[Required]
public string Naam { get; set; }
[Required]
public string Omschrijving { get; set; }
[Required]
public double Prijs { get; set; }
private List<Categorie> categorien = new List<Categorie>();
public virtual List<Categorie> Categorien
{
get { return categorien; }
set { categorien = value; }
}
[Required]
public byte[] Plaatje { get; set; }
}
NOTE: There are virtual properties in there so that my entity framework creates a merging table. Normally it links all the categorie's to the products and vice versa.
And my rest looks like:
// GET api/Rest/5
public Product GetProduct(int id)
{
Product product = db.Producten.Find(id);
Product newProduct = new Product();
if (product == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
else
{
product.Categorien = null;
}
newProduct.Id = product.Id;
newProduct.Naam = product.Naam;
newProduct.Omschrijving = product.Omschrijving;
newProduct.Plaatje = product.Plaatje;
newProduct.Prijs = product.Prijs;
newProduct.Categorien = product.Categorien;
return newProduct;
}
First problem: I cannot send any product aslong as it has a categorie. I have to make it null.
Second problem: I cannot send the original product because of the first problem.
I am assuming your problem is with a circular reference during serialization, since categories reference multiple products and products reference multiple categories. One solution is to use Data Transfer Objects (DTO) instead of returning the straight entities you are using for EF. To make it easy to map your entities to the DTO's I would use AutoMapper. This is essentially what you are doing when you create an instance of newProduct in your REST API method, but AutoMapper takes the hard coding and drudgery out of mapping. Your DTO for a product would look very similar but they would not have the virtual navigation properties or the attributes needed by EF. A DTO for a product would look something like this.
public class Categorie
{
public int Id { get; set; }
public string Naam { get; set; }
public string Omschrijving { get; set; }
public byte[] Plaatje { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Naam { get; set; }
public string Omschrijving { get; set; }
public double Prijs { get; set; }
public List<Categorie> categorien = new List<Categorie>();
public List<Categorie> Categorien
{
get { return categorien; }
set { categorien = value; }
}
public byte[] Plaatje { get; set; }
}
Notice that the DTO for Categorie does not contain a list of products, since in this case you want a listing of products. If you keep the field names the same for your DTO's as your entities AutoMapper will handle the mapping automatically. I usually keep the same class name for the DTO's and just distinguish them from the entities by having a different namespace. Your REST API method would look something like this.
// GET api/Rest/5
public Product GetProduct(int id)
{
Product product = db.Producten.Find(id);
return Mapper.Map<Product, Dto.Product>(product);
}
I have the following relationship between the entities.
Company 1 ---* Appointments *---1 Employee
I have the .net asp membership in a separate database. Whenever a user is created it can be assigned to companies, employees, or administrators roles.
in the Index action of my Company Controller, I check the logged in user's role. Based on the role, I make different linq query. For example, administrators can get list of all companies, companies can get list of company which has a username property (string) same as the User.Identity.Name. For both of administrators and companies role, it is working fine.
For the employees role, I want to load all the companies that are related to the current employee. I am having hard time to compose a linq query that does this job.
i tried
var companies = db.Companies.Include(c => c.Appointments.Select(a=>a.Employee).Where(e=>e.Username.ToLower() == this.User.Identity.Name.ToLower())).ToList();
to which i get this error
"The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
Parameter name: path"
Here are the source code,
CompanyController
[Authorize]
public class CompanyController : Controller
{
private MyDBContext db = new MyDBContext();
//
// GET: /Company/
public ViewResult Index()
{
var viewModel = new CompanyIndexViewModel();
if (Roles.IsUserInRole("administrators")) {
viewModel = new CompanyIndexViewModel { Companies = db.Companies.ToList() };
}
else if (Roles.IsUserInRole("companies")) {
viewModel = new CompanyIndexViewModel { Companies = db.Companies.Where(c => c.Username.ToLower().Equals(this.User.Identity.Name.ToLower())).ToList() };
}
else if (Roles.IsUserInRole("employees")) {
var companies = db.Companies.Include(c => c.Appointments.Select(a=>a.Employee).Where(e=>e.Username.ToLower() == this.User.Identity.Name.ToLower())).ToList();
viewModel = new CompanyIndexViewModel { Companies = companies.ToList() };
}
return View(viewModel);
}
...
Models
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace TorontoWorkforce.Models
{
public class Company
{
public int CompanyId { get; set; }
[Required]
public string Username { get; set; }
[Display(Name="Company Name")]
[Required]
public string Name { get; set; }
[UIHint("PhoneNumber")]
public string Phone { get; set; }
[DataType(DataType.Url)]
public string Website { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
public AddressInfo AddressInfo { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
public virtual ICollection<Appointment> Appointments { get; set; }
public Company(){
this.AddressInfo = new AddressInfo();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace TorontoWorkforce.Models
{
public class Appointment
{
public int AppointmentId { get; set; }
[Required]
[UIHint("DateTime")]
[Display(Name="Appointment Date")]
public DateTime? DateOfAppointment { get; set; }
[Required]
public int CompanyId { get; set; }
[Required]
public int EmployeeId { get; set; }
[Required]
[UIHint("MultilineText")]
[Display(Name = "Appointment Summary")]
public string Description { get; set; }
[Display(Name="Allocated No of Hours")]
public decimal NoOfHoursWorked { get; set; }
public virtual Company Company { get; set; }
public virtual Employee Employee { get; set; }
public virtual ICollection<AppointmentLine> AppointmentLines { get; set; }
public Appointment() {
//this.AppointmentLines = new List<AppointmentLine>();
this.DateOfAppointment = DateTime.Now;
}
[NotMapped]
[Display(Name="Actual No of Hours")]
public decimal ActualHoursWorked {
get
{
decimal total = 0;
foreach (var jobline in this.AppointmentLines)
{
total = total + jobline.TimeSpent;
}
return total;
}
}
}
public class AppointmentLine
{
public int AppointmentLineId { get; set; }
[UIHint("MultilineText")]
[Required]
public string Description { get; set; }
[Display(Name="Time Spent")]
[DataType(DataType.Duration)]
public decimal TimeSpent { get; set; }
public int AppointmentId { get; set; }
public virtual Appointment Appointment { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace TorontoWorkforce.Models
{
public class Employee: TorontoWorkforce.Models.Person
{
public int EmployeeId { get; set; }
[Required]
public string Username { get; set; }
[Display(Name="Date Hired")]
public DateTime? DateHired { get; set; }
[Required]
public string Position { get; set; }
public virtual ICollection<Appointment> Appointments { get; set; }
public Employee() {
this.DateHired = DateTime.Now;
}
}
}
If you want to get companies which have appointment with selected employee you don't need to use Include. Include is for instructing EF to load all appointments related to the company (and it doesn't support filtering). Try this:
string userName = this.User.Identity.Name.ToLower();
var companies = db.Companies.Where(c => c.Appointments.Any(a =>
a.Employee.Username.ToLower() == userName)).ToList();
I think you just have an end parentheses in the wrong place. You need one more after "a => a.Employee" and one less after "this.User.Identity.Name.ToLower()));"
Try this code:
var companies = db.Companies.Include(c => c.Appointments.Select(a=>a.Employee)).Where(e=>e.Username.ToLower() == this.User.Identity.Name.ToLower()).ToList();
Edit:
You should also be able to use the standard string include method:
var companies = db.Companies.Include("Appointments.Employee").Where(e=>e.Username.ToLower() == this.User.Identity.Name.ToLower()).ToList();