Consider the following domain model:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int AddressId { get; set; }
public Address Address { get; set; }
public ICollection<Message> Messages { get; set; }
}
public class Address
{
public int Id { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
.....
}
public class Message
{
public int Id { get; set; }
public Message Message { get; set; }
}
We use EF6 code first to create the database for this model. The address data is saved to its own table.
On top of EF6 we use ASP.NET Web API 2.0 and OData.
The ODataConventionModelBuilder creates an EntitySet for Employee and a ComplexType for Address.
In order to return an Employee we created the following action:
[EnableQuery]
public SingleResult<Employee> Get([FromODataUri] int key)
In order to always return the Address we use an EF Include on the Address property. So when we use the following url:
/api/Employees(1)
The Employee with id 1 will be returned with its Address property filled.
However, when we use $expand to expand the Messages for the use we loose the Address data:
/api/Employee(1)?$expand=Messages
Returns an Employee object with Messages expanded, but without the Address data. The EnableQuery attribute applies the expand to our query-able but overwrites the EF Includes that we specified in the API.
We cannot expand the Address too from the client because Complex types cannot be expanded in OData.
When we change the Address Odata type to an Entity we need multiple calls to create or update an Employee. One call to an Address controler which handles create/update for addresses and one call to the Employee controller to handle create/update for employees.
So my question: Is it possible to preserve the Address include without specifying it from the client?
We use element type like Employee when select all properties, so we lose your include Address when composing the Linq expression, but if you select Address, it will come in result because it's not select all, so a work around maybe request like: $expand=Messages&$select=Address,Id,Name
Relative code: https://github.com/OData/WebApi/blob/master/OData/src/System.Web.OData/OData/Query/Expressions/SelectExpandBinder.cs#L272-L292
Related
Background:
Given the following two entities joint through one to one relationship:
public partial class Parent
{
public long Id { get; set; }
public string Email { get; set; }
public virtual Details Details{ get; set; }
}
public partial class Details
{
public long Id { get; set; }
public long ParentId{ get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Dob { get; set; }
public virtual Parent Parent { get; set;}
}
And having the following Query model:
public class Query
{
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime? Dob { get; set; }
}
Question
How can I apply the Query as IQueryable on the Parent (or Details) entity?
Notes based on the use case I have:
Query class can't have two sub-classes for Parent and Details (it should be flattened)
DB SQL query should fetch results that matches both conditions in Parent and Details (if condition fail for details, then parent shouldn't be in the results).
There might be long list of optional fields in Query model. It means that the DB query should be dynamic and smart enough to know how to build the query and to know each field in Query belongs to which entity Parent or Details (i.e. I don't want a solution where I add conditions to check whether Dob exist in the Query or not)
Use case:
I'm using HotChocolate framework to integrate GraphQL which uses expression trees to build the queries. The issue I'm trying to solve is mentioned here
Your support and suggestions would be highly appreciated!
I managed to handle this issue by creating a View on the DB which joins both tables to act as single entity where I can filter, paginate and sort regardless whether it is one to one linked tables or it is single table.
Then did reverse engineering of that view using this link to be integrated with EntityFramework and finally managed to handle IQueryable on both entities.
In case of any other option available and suitable for HotChocolate, please add another answer to enhance my existing solution.
Giving the sample below, is there any way to have Address in the same table as User without making use of table splitting or owned types (eg like EF6 complex types)? The generated SQL prevents me from using it and complex types does not seem to be supported in EF Core 3:
public class User
{
public int Id { get; set; }
public Address Address { get; set; }
public string UserName { get; set; }
}
public class Address
{
public string StreetAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
The only other options I see would be to map Address to its own table.
I will use a 1 to 0..1 relationship or include the properties in User directly.
Nevertheless, using Owned Types as a replacement for ComplexTypes like in EF 6 is horrible , if not completely useless from a SQL perspective, and I cannot see any reason for the joins. Maybe someone can clarify a proper justification for completness
What is the difference between foreign key reference using Id vs object.
For example:
FK relation using Id
class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public int CategoryId { get; set; }
}
vs
FK relation using object
class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public Category Category { get; set; }
}
I have noticed from database that by using Id property the column essentially becomes non-null field.
Is that the only difference?
I think we can't query multiple tables at once i.e.querying related data?
When should I choose to use either options?
In your first example you're not adding a relationship, just an integer property named CategoryId.
In your second example, Entity Framework will create an integer column named "Category_ID", but you will be not be able to see this property in your model, so I like to explicitly add it my self and be able to use it along with the navigation property.
class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
[ForeignKey("Category")]
public int CategoryId { get; set; }
public Category Category{get;set;}
}
This way you can also control the data type of CategoryId, so you could make it optional (nullable)
public int? CategoryId { get; set; }
*The foreign key data annotation is not needed, unless you have property or navigation property names that do not follow the naming convention for foreign key property names (Bardr), it doesn't harm to explicitly declare it either for clarity purposes
This implies that you're creating a 1 to many relationship (1-*) with products and categories, so in your Category class you would be adding a collection navigation property for products
class Category
{
public int Id{ get; set;}
public string Name{ get; set; }
...
public ICollection<Product> Products{get; set;}
}
Basically it depends on your use case and what type of loading related data you choose. Whether you use Id or object reference or full relationship on both sides (Id and object) it depends on your overall application architecture. If you wil go and use full or object reference everywhere, you will (probably) end up with a mess, and you won't know whether you should query for some entities using their repository or if it'll be okay to include them to some other query. I highly recommend you to take a look at this book, especially chapter 19 (Aggregates) and 21 (Repositories). There you have an in-depth explanation of what I meant and much more. (This does not only apply to applications built in DDD way)
There are 2 tables in my database,
1. Employee, The model is below.
public class Employees
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int DepartmentId { get; set; }
public string Designation { get; set; }
public DateTime DateOfBirth { get; set; }
[ForeignKey("Id")]
public virtual IList<Address> Address { get; set; }
}
I have another table named Address. The model is as below.
public class Address
{
public int Id { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PinCode { get; set; }
}
An employee can have multiple address. I am dynamically adding controls for adding address. I am using Entityframe work for all the operations like creating DB, Updating etc.
The code I use for adding the data to database is as follows.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(string command,[Bind(Include = "Id,FirstName,LastName,DepartmentId,Designation,DateOfBirth")] Employees employees, [Bind(Include = "Id,AddressLine1,AddressLine2,City,State,PinCode")] List<Address> address)
{
if (ModelState.IsValid)
{
foreach (var item in address)
{
db.Address.Add(item);
}
db.Employees.Add(employees);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(employees);
}
When I insert data, the data is getting inserted in the database but I am getting the following exception.
An exception of type 'System.InvalidOperationException' occurred in EntityFramework.SqlServer.dll but was not handled in user code
Additional information: The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: Unable to set field/property Address on entity type GridSample.Models.Employees. See InnerException for details.
The Innerexception :
{"Unable to cast object of type 'GridSample.Models.Address' to type 'System.Collections.Generic.IList`1[GridSample.Models.Address]'."}
Please help me to sort out this issue.
I think you cannot use ForeignKeyAttribute on a property in the "one" class in one-to-many relation. It should be used on a property in the "many" class to specify the key of the "one" class instance. So, in your case, you should modify your entity classes to reflect the relationship.
public class Employees
{
//...
[InverseProperty("Employee")]
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
//...
public int EmployeeId { get; set; }
[ForeignKey("EmployeeId"), InverseProperty("Addresses")]
public virtual Employees Employee { get; set; }
}
Here the ForeignKeyAttribute is used to decorate the navigation property and specify the key property. Otherwise, you can decorate the key property and specify the navigation property, like so:
public class Address
{
//...
[ForeignKey("Employee")]
public int EmployeeId { get; set; }
[InverseProperty("Addresses")]
public virtual Employee Employee { get; set; }
}
EDIT
The reason you're not getting any addresses is because you're adding employee and addresses to the database independently, i.e. without indicating the relation between them. There are several ways to do that, and I think the simplest is to make use of the navigation properties:
Either I: Add the addresses to the employee's address collection rather than directly to a dbset:
foreach (var item in address)
{
employees.Addresses.Add(item);
}
db.Employees.Add(employees);
db.SaveChanges();
Or II: For each address specify the employee to whom it belongs:
foreach (var item in address)
{
item.Employee = employees;
db.Address.Add(item);
}
db.Employees.Add(employees);
db.SaveChanges();
one for customer, and one for address.
Required Functionality When a customer registers, they enter their personal details such as name, tel as well as their address details in the same view.
Current Functionality
At present, EF scaffolding provides a dropdown list of addresses to choose from.
Current Code
public class Customer
{
public int CustomerId { get; set; }
public string FirstName { get; set; }
[Required]
public string Surname { get; set; }
[Required]
...
Customer Fields
...
public int AddressId { get; set; }
public virtual Address Address { get; set; }
}
public class Address
{
[Key]
public int AddressId { get; set; }
...
Address Fields
...
// Navigation Properties
public virtual List<Customer> Customer { get; set; }
}
When seeding the database, I can do so as follows:
new List<Customer>
{
new Customer
{
** Customer Fields ** ,
Address = new Address { ** Address Fields ** }
}
}.ForEach(c => context.Customers.Add(c));
base.Seed(context);
My thoughts
My initial thoughts are that I should create a 3rd Data model called CustomerWithAddress which is essentially a composite of customer and address models. This would allow me to scaffold a strongly typed view.
Alternatively, is it possible for a controller to pass 2 models to 1 view?
I don't know if this is the best way of tackling this problem, or in fact if it is possible. Any thoughts?
If your customer model has an address property, then you will be able to access it from your view. e.g.
#Html.DisplayTextFor(x => model.Address.Addressline1)
Your viewmodel Idea is a good one, but it's not necessary for this particular case.
EDIT: As my friend below pointed out, you may need to manually load the Address property if you are employing lazy loading.