I'm following Domain Driven Design for this project.
I have got an object containing an image. Let's call it Product:
class Product {
UniqueID id;
ProductName name;
ImageBytes imageBytes;
}
UniqueID, ProductName and ImageBytes are just validated objects that represent respectively a String, a String and a List<int>.
I would like to store the actual image on Firebase Storage and saving the imageId on Firestore.
So, in my idea, I have an image on Firebase Storage with id xYF87Ejid0093RTcxaWpof and a Doc in the Firestore containing this id instead of the actual image.
The problem I'm stuck into is writing the Data Transfer Object of a Product. How should I convert an imageId into the actual image?
Please consider that I'm using DDD, so my DTO and my Entity classes are Unions (using Freezed).
I think I should have an intermediate class called FirestoreProduct at the Infrastructure level that looks like this:
class FirestoreProduct {
UniqueID id;
ProductName name;
UniqueID imageId;
}
So that I can write a DTO that uses this class instead and I can create the Product object from the repository class after I downloaded the image.
Is there any better way to solve this problem in the DDD way?
Thanks in advance.
Do you really need the ImageBytes to perform the business logic of your product entity? I even guess that your Product is an aggregate root and thus will have data and corresponding behaviour (business logic) in it.
So from my point-of-view the model of your FirestoreProduct is closer to a domain model than your Product class.
I consider your image a separate aggregate which can reside in the same service but a different storage or which could even live in a separate service.
Either way the Product aggregate should only need a reference to the image. I would model it somehow like this
class Product {
ProductId id;
ProductName name;
ImageId imageId;
}
whereas ProductId and ImageId would be value objects for strongly-typed ids.
I expect the storing/upload of a new image to be performed in a separate transaction than creating/updating a product itself. That means when you create a new product or perform some business logic on it to change it your image has already been uploaded to the Firestore and you only work with the image id in your product aggregate.
Your Product DTO (you could also call it view model) on the other hand which you use for providing data for the UI (i.e. for reading data) can look different then the Product aggregate. This is okay and also makes total sense.
So the DTO would look something like this instead:
class ProductDto {
UniqueID id;
ProductName name;
ImageBytes imageBytes;
}
Note: I don't know if ImageBytes is the right type for the DTO as my flutter knowledge is limited but I hope you get the idea.
With that you can bypass the Product aggregate domain repository completely and have another service class which will give you all the data you need for reading/viewing the Product data. As you do not change anything by reading data you do not go through your domain model and optimize for reads.
The code which than builds the DTO will go to your persistence for querying some Product data but also to Firebase for querying the actual image. You could even reload the actual Firebase image afterwards by a separate call from the UI if performance is an issue, for instance if you retrieve a whole list of product data for reading at once.
Supposing I have two tables Meetings and Attendees.
My database looks like this.
// Table Meeting
Id
Description
// other properties ...
// Attendee
Id
Name
MeetingId
// other properties ...
I have two views that are mapped to these tables. One is ViewMeetings with just the meeting data the other ViewMeetingAttendees joined with Attendees.
I want to use table per type(TPT), mapping each table to a view.
public abstract class MeetingBase
{
// Some properties here
}
public class ViewMeeting : MeetingBase
{
}
public class ViewMeetingAttendee : MeetingBase
{
public String AttendeeName { get;set; }
}
// Configuration
moduleBuilder.Entity<ViewMeeting>().ToTable("ViewMeetings");
moduleBuilder.Entity<ViewMeetingAttendee>().ToTable("ViewMeetingAttendees");
// NOTE fixed the ViewMeeting error as stated in HansVG answer below.
Every time I try to run this code I get an error Invalid Column Name "Discriminator"
I understand that the entity framework is trying to resolve the types as a table per hierarchy(TPH). However, I still want to map the properties using inheritance without the inferred TPH. This is reasonable since all the columns are the same except for one. I have ten other columns and two views. Also I have a single meeting entity needing most of the same columns for CRUD operations.
Is there a way to keep the inheritance but lose the discriminator error? [NotMapped] is not an option since I am still pulling the data from the database. Also, I don't prefer to join the tables locally using LINQ since there are joined entities that don't need to be mapped otherwise.
You defined 'ViewMeeting' twice and didn't configure 'ViewMeetingAttendee'.
Your configuration should be:
moduleBuilder.Entity<ViewMeeting>().ToTable("ViewMeetings");
moduleBuilder.Entity<ViewMeetingAttendee>().ToTable("ViewMeetingAttendees");
I have the following:-
Visual Studio 2013.
i created a new asp.net MVC-5 web project.
the project is using asp.net identity 2.2.
for the authentication method i chose "Individual user accounts"
this process created a new database named aspnet-OurProjectNanme-number
inside the automatically generated database, i have a table named AspNetUSers which store the user info.
now i am working on building an ERP system. and inside the ERP system i want to add the following:-
a table named "Asset" to store the asset info.
the "Asset" table will have 2 columns named "CreatedBy" + "ModifiedBy" which should store the userId who created and modified the asset item.
now i am not sure how i need to achieve this? as i need to add a foreign key between my custom table "Asset" and the "AspNetUsers" table which have been created automatically.. so can i add my custom table "Asset" inside the automatically generated database, and build the foreign key between the Asset.CreatedBy and AspNetUsers.Id ??
if the answer is Yes then can this relation break in the future if we want to upgrade our aspnet identity version ? as upgrading the identity might result in creating new tables or renaming existing ones etc.. which might break the relation between the Asset table and the AspNetUsers table?
If the answer is No (i should not add custom tables inside the automatically generated database ) then how i can build the foreign key ?? and where i need to add the Asset table in this case??
The most common approach to what you want to do is simply to add your additional model as a DbSet in your ApplicationDbContext.
public class Asset
{
public string CreatedBy { get; set; }
public string UserId { get; set; }
public ApplicationUser User { get; set; }
}
public class ApplicationUser : IdentityUser
{
public IList<Asset> Assets { get; set; }
}
public class ApplicationDbCotext : IdentityDbContext<ApplicationUser>
{
public DbSet<Asset> Assets { get; set; }
}
As I mentioned this is the most common approach as updating the Identity packages should have no impacting affects on your schema. That said you should always test updates before pushing to production.
UPDATE:
Note that when you're working with One to Many relationship's you will see in our Asset model a property for the User Id foreign key as well as the User object. Because of the relationship we are then able to create a List<Asset> in our User to complete the One to Many relationship. This will then allow us to directly query Assets belonging to a User.
As for Code First vs Database First the difference really comes down to how you define the mapping between Entity Framework and the Database.
As I mentioned below there is no one size fits all answer to should you separate the Identity context from your business context, or should you separate them into separate databases. The reality is that only you can answer that question for your needs. It is far more common to have all of the data in a single database. That said, there is something to be said for the security of having identifying information about a user such as their name, email and password hash separated from information like their address or payment information. The trade off is that you can find yourself trying to maintain objects that are supposed to be tied together but are only loosely related because they reside in different databases. Also you would then need to make sure you're using different users/passwords to connect to the different databases, and it's better to have the databases on different servers because if the server gets compromised you went through the entire exercise for nothing. The trade off to get the theoretical security ends up being so impractical with there consistently being another thing you have to do, that you end up seeing everything in one database where you can focus all of your hardening efforts.
Both the ApplicationDbContext and ApplicationUser objects should typically be created for you when you File -> New a project with Individual Authentication. You can add as many properties and relationships to your User as you require.
-- Update --
The answer is growing and growing and so is the discussion. I think I've shown all kinds of variations, which may not have helped to make it understandable. So here is a summary. For explanation, read the full answer and discussion.
Out of the box you have two contexts, identity and business. These are decoupled, so you can change your security without interfering with your business. This way, upgrading security won't break your application or other models. Since the contexts are seperate, changes to either one of them won't affect the other.
As a sidenote: you are not intended to directly access the AspNet identity tables. Implement the UserManager and use the avaiable methods of the manager to perform actions.
Now it comes to logic, where should information be stored? As a simple rule just ask yourself the question: is it part of security or business?
In both contexts you have users. For your requirement this is a logical 1:1 relation. But they are actually seperate. You can create people without supplying a login or delete a login, without deleting a user (people), e.g. for historical reasons.
All you want is to find all information for the current user. So all you need is the People.Id.
Without having to change the IdentityUser you can create the 1:1 relation by just overriding the AspNetUser.Id.
var appUser = new IdentityUser
{
UserName = model.Email,
Email = model.Email,
Id = Convert.ToString(People.Id)
};
var identityResult = await userManager.CreateAsync(appUser, model.Password);
You do not need the identity context for your business. All you need is People.Id. The identity context is only used when tokens are issued and users are created / modified.
To obtain the id use something like this:
var peopleId = int.Parse(Request.User.Identity.GetUserId());
Now you can query your business model using the Id.
When registering, extend the View and ViewModel with the People information you want to store. This will allow you to add both People and AspNetUser at the same time. Though this is not one transaction. But I think it is highly unlikely that creating either one would fail if you perform checks first.
You can validate the username and password (use the methods in the UserManager) and check the ModelState of the viewmodel before creating the user. Use attributes to force Required fields to be filled.
-- Original answer --
In order not to repeat myself, read my answer here.
In short, keep identity and business seperated.
Just in case the identity logic is removed from the same database, like when implementing IdentityServer.
It seems you have business information in AspNetUser. If so, create a Person table and move the information to that table. Relate to that table in your model. In table Person you can add a reference to AspNetUser.
-- update --
I think you understand correctly, but I will just add the details to this answer.
In most cases all tables are defined in one database. But that doesn't mean they are all part of the same model. There can be multiple contexts. In this case one for Identity and one (or more) for Business.
Now why seperate those two? The most important difference between the Business model and Identity model is that Identity tables are not to be called directly. We use the Owin context to call the UserManager / RoleManager.
That is why we cannot add these tables to the business model. Things can be altered in a way that is not secure. Also we do not want the business to have any knowledge about authorization. It shouldn't matter how this is done, as long as a user is identified and authorized.
Also you may want to implement OpenId and claim based authorization. In that case information doesn't have to be available in the database.
The idea is to create a 1:1 relation of the identity table AspNetUsers and business table People. There can be some redundancy, like email or (user)name. But that isn't a problem. The People table should contain all information you want to use in your business model. And the business tables should only relate to People, not AspNetUsers.
Now about the link between AspNetUsers and People. There are four options:
Set People.Id = AspNetUser.Id. Please note that AspNetUser.Id doesn't have to be a GUID. You can add your own value as key.
Set AspNetUser.Id = People.Id.
Add column AspNetUserId to People. No modifications to Identity are needed. You can add People to the Identity Model as well, but I don't think you can create both records in one transaction. You can use User.Identity.GetId() to get AspNetUser.Id. You may however ask yourself if the business should have knowledge about this information.
Add column PeopleId to AspNetUsers. You'll need to extend the IdentityUser to add PeopleId. An advantage is that you don't need the AspNetUser Id, but you can use the actual Id of People. When using OpenId or claims you can get People.Id from claims and you won't have to add AspNetUser.Id to the business. Optionally you can add People to the Model and as navigation property of the extended IdentityUser. When creating the user, you can do this in one transaction.
In case you are creating the user in seperate contexts, you'll need to handle the rollback yourself. But before adding a record to People, you can already test if an AspNetUser can be added: has a valid name/email and password.
Since your business model relates to the People table, you can query all assets and join with the People table for additional information. Or you can get all assets for the current user.
o yes, there are two contexts. The identity model, which contains the AspNet... tables + optionally People. And the business model, which contains all ERP tables + Asset + People.
You may consider to use code first for identity framework 2 and database first for the business model.
I hope that this helps. If not, let's continue in chat.
-- update --
The answer focused on seperation of domains: identity and business. That is why I didn't discuss one possible alternative concerning the AspNetUsers table.
The two models are representations of the database, which means that the database doesn't have to be an exact match. You are free to map tables and fields as you like, as long as they don't break database logic.
Since AspNetusers and People has a 1:1 relation and when both tables are present in the same database, you may as well merge the two into the AspNetUsers table. You can also add relations to the AspNetUsers table, though you may want to add an extra Id (int) column instead of using the current Id (string).
This does not mean the People class can be discarded, except that we have to change the table mapping: AspNetUsers.
Example:
[Table("AspNetUsers")]
public class People
{
[Required]
[StringLength(128)]
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
As you can see, the sensitive fields are not mapped. We need however the Id field. You can now read and update the mapped fields.
You don't have to extend IdentityUser. You can add an AspNetUser and then update the fields using People in the other context. But if you want to add a user in one single transaction it may be easier to extend the IdentityUser (make sure you'll define the new fields in both People and ApplicationUser):
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
There are multiple advantages:
There is only one transaction to add the user.
You cannot expose the sensitive fields since they are not mapped in People.
You cannot add People to the database, since some required fields are not mapped in People.
Please note that this may not work for all types of models (code first/database first + migrations).
My overall goal is to be able to display all Customer Information in my CustomerController->Details View and have no idea how to pass all of this data:
Customer info - (fields directly in Customer table)
Customer contacts
Customer locations
Customer documents
I am able to display the customer info just fine because I am doing something like this and passing it to my view:
public ActionResult Details(int? id)
{
Model.Customer customer = _custService.GetCustByID(id);
return View(customer);
}
I have absolutely no idea how to go from just this which gives me access to direct Customer properties in my view to displaying all of these other lists of related customer items that come from separate tables.
Here are a few details on my project setup:
I am using EF6DbFirst and I also create a Dto for each one of my entities and use AutoMapper to map everything. My Customer.cs model has all of the customer's direct properties and also a couple that look like this for the things that are one-to-many relationships:
public List<CustomerContact> Contacts { get; set; }
Now in my AutoMapper config I did the following since my table is named CustomerContacts in EF:
CreateMap<Customer, Model.Customer>()
.ForMember(dest => dest.Contacts, opt => opt.MapFrom(src => src.CustomerContact));
CreateMap<Customer_GetAll_Result, Model.Customer>();
I am not sure if this is the proper way to do this with AutoMapper or if there is anything else I have to tell it when accessing other tables. I am thinking the only reason I have to map this property is because I want to change the name to just c.Contacts rather than c.CustomerContacts.
Side question:
As you can see I am also trying to map values that come from my GetAll stored procedure which my custService.GetAll() uses in my controller to bind a grid of customers. I don't think this matters in this case but I am assuming if I ever need to loop through that list that comes from the proc and get the contacts for each customer they will not be available since my stored proc only returns direct customer properties which is why I can't do the mapping for that one. Is there any workaround for this?
I'm pretty new to MVC 2 using the Entity Framework. I have two tables Company {ID int identity PK,Name nvarchar} and User {ID int identity PK,UserName nvarchar,CompanyID int FK}. A Foreign Key exists between User and Company.
I generated my ADO.NET Entity Data Model, a Controller and a view to insert a record. My HTML form has the fields Company and UserName and the idea is when I click save a Company and User is inserted into the database. Sounds straight forward right!
My question is as follows:
I created a strongly-typed view derived from my 'User' entity. I'm using the the html helper Html.TextBoxFor(model => model.Organisation.Name) but the html name attribute for this input field is 'Organisation.Name'. My problem with this is that the dot throws up all sorts of issues in JQuery, which sees this as a property. If I want to change the name I read that I can use DataAnnotations but because I used the Entity Designer this involves using Buddy Classes. Seems like a bit of overkill just to change the html name attribute on this input field. Am I approaching this the right way or am I missing something here?
Thanks for the help !
I resolved this by taking a step back and reevaluating the way I was structuring my data. The end result was that my business entities were too closely coupled to my database schema and didn't reflect the domain I was working in. I redesigned my app. using POCO's to represent my business entities that better reflected my domain and this had the effect of 'flattening' the relational structure in this scenario, so instead of model.Organisation.Name I now have model.OrganisationName.