I'm having a trouble do the following mapping:
Domain (simplified version):
public class Ad
{
// Primary properties
public int Kms { get; set; }
// Navigation properties
public virtual Model Model { get; set; }
}
DTO:
public class CreateAdDto
{
// Primary properties
public int Kms { get; set; }
// Navigation properties
public virtual ModelDto Model { get; set; }
}
ViewModel:
public class CreateAdViewModel
{
// Primary properties
public int Kms { get; set; }
public int Make_Id { get; set; }
public int Model_Id { get; set; }
// Navigation properties
public IEnumerable<SelectListItem> MakeList { get; set; }
public IEnumerable<SelectListItem> ModelList { get; set; }
}
In the Controller, when I do the Mapping I'm loosing the Make_ID from the Dropdownlist of the View:
public virtual ActionResult Create(CreateAdViewModel adViewModel)
{
if (ModelState.IsValid)
{
var adDto = Mapper.Map<CreateAdViewModel, CreateAdDto>(adViewModel);
_adService.CreateAd(adDto);
}
return RedirectToAction(MVC.Home.Index());
}
The mapping is:
Mapper.CreateMap<CreateAdViewModel, CreateAdDto>()
Thanks.
As you have mentionned, the Ad need to know the Model_Id and to set it into the Model
Mapper.CreateMap<CreateAdDto, Ad>()
.ForMember(dest => dest.Model, opt => opt.MapFrom(src => new Model { Id = src.Model_Id }));
You also need from the other mapping side to let the Dto know where to get the Model id.
Mapper.CreateMap<Ad, CreateAdDto>()
.ForMember(dest => dest.Model_Id, opt => opt.MapFrom(src => src.Model.Id}));
The code above is not secure because a validation to see if Model is null should be added.
For the rest of your code, you seem to do it right. The section with Entity Framework requires you to attach because the entity Model already exist, otherwise, EF would insert this entity to the database.
CreateAdDto doesn't have a Make navigation property or a Make_Id property.
Solution found after some research:
ViewDomain:
public class CreateAdViewModel
{
// Primary properties
public int Kms { get; set; }
public int Make_Id { get; set; }
public int Model_Id { get; set; }
// Navigation properties
public IEnumerable<SelectListItem> MakeList { get; set; }
public IEnumerable<SelectListItem> ModelList { get; set; }
}
DTO:
public class CreateAdDto
{
// Primary properties
public int Kms { get; set; }
public int Model_Id { get; set; }
// Navigation properties
//public virtual ModelDto Model { get; set; }
}
Domain:
public class Ad
{
// Primary properties
public int Kms { get; set; }
// Navigation properties
public virtual Model Model { get; set; }
}
Viewmodel -> Dto Mapping:
Mapper.CreateMap<CreateAdViewModel, CreateAdDto>();
Dto -> Domain Mapping:
Mapper.CreateMap<CreateAdDto, Ad>()
.ForMember(dest => dest.Model, opt => opt.MapFrom(src => new Model { Id = src.Model_Id }));
Atention:
To achieve this with Entity Framework, I had to attach first the Model Entity to the Context and then Ad the new Ad:
public void CreateAd(CreateAdDto adDto)
{
var adDomain = Mapper.Map<CreateAdDto, Ad>(adDto);
_modelRepository.Attach(adDomain.Model);
_adRepository.Add(adDomain);
_adRepository.Save();
}
Hope this is the best practice.
BTW I would like to have some opinions regarding this aproach.
Thanks.
Based on what is I see in your question, I suggest a simple approach. Your application is medium scale. You should very carefully about maintainability,my experience say.So try to create a simple an strain forward approach for yourself like below approach:
I can describe all layer in detail but with notice to title of your question I prefer describe only Model(bussiness Ojbect) layer:
Good! As you can see PM.Model include:
Tow sub folders contain our ViewModels and in root of Library we have a .tt file contain Entity framework Objects (POCO classes) and we have a Mapper folder(Since that i dont like use autoMapper or third party like this :) ).
You can see IListBox interface in Domain layer. I put all ListBox container to this interface.
I hope current approach useful for you but finally I suggest remove one of this layers DTO or ViewModel, because in the future will be very complex.
Good luck
Do you aware about cost of these mapping?! You have 2 layers mapping (before arrived to Entity framework) for an simple insert.We can do more complex CRUD(s) in less than 2 layers mapping.
How to think about maintainability of this code?
Please keep DRY,KISS,SOLID conventions in your mind and top of your everyday work.
Good luck
Related
Note: Technoligies in use are ASP.Net MVC 3, Entity, SQL Server Management Studio
Problem?
It seems that when I run, the context as: public class DatabaseInit : DropCreateDatabaseAlways<LocationAppContext>
That it creates the database, but my service assignments table has an extra foreign key called
ServiceAssignment_Service when it shouldn't.
My service assignment model is as such:
namespace LocationApp.Models
{
public class ServiceAssignment
{
public int id { get; set; }
public int locationID { get; set; }
public int ServiceID { get; set; }
public virtual Location Location { get; set; }
public virtual ServiceAssignment Service { get; set;}
}
}
and the service model is as such:
namespace LocationApp.Models
{
public class Service
{
public Service()
{
this.ServiceAssignments = new HashSet<ServiceAssignment>();
}
public int id { get; set; }
public string name { get; set; }
public string description { get; set; }
public bool active { get; set; }
public string icon { get; set; }
public virtual ICollection<ServiceAssignment> ServiceAssignments { get; set; }
}
}
with that said, the relation ship is simple:
service assignments have many location id's and service id's.
why is this extra foriegn key being generated? the curent keys, that should e there is:
PK: Main PK for the table
FK 1: Location_ServiceAssignment
FK 2: Service_ServiceAssignment
Those are their, how ever this third one is baffling....
The second part is: If a location of id 2 has a service id of 2,3,6,7 How do I get all service id's returned, such that I can pass the object to a service query to get all information on the service based on the ID?
Update:
Context Class:
namespace LocationApp.DAL
{
public class LocationAppContext : DbContext
{
public DbSet<Content> Contents { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<ServiceAssignment> ServiceAssignments { get; set; }
public DbSet<Service> Services { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Location>().HasMany(sa => sa.ServiceAssignments);
modelBuilder.Entity<Service>().HasMany(sa => sa.ServiceAssignments);
}
}
}
I think you have to tell EF that Service.ServiceAssignments is the inverse navigation property of ServiceAssignment.Service and that Location.ServiceAssignments is the inverse of ServiceAssignment.Location. Right now with your mapping you only specify that Location or Service has many ServiceAssignments. EF will consider the navigation properties in ServiceAssignment as the ends of separate relationships.
Try instead the mapping:
modelBuilder.Entity<Location>()
.HasMany(l => l.ServiceAssignments)
.WithRequired(sa => sa.Location)
.HasForeignKey(sa => sa.LocationID);
modelBuilder.Entity<Service>()
.HasMany(s => s.ServiceAssignments)
.WithRequired(sa => sa.Service)
.HasForeignKey(sa => sa.ServiceID);
You can probably remove this mapping altogether as an alternative because EF should detect the right relationships by convention.
So, use either no mapping (=mapping by convention) or the full mapping (=specifying both ends of the relationships). Just the 50%-mapping you have used is likely the problem.
If I have the following objects:
public class Application
{
public int ApplicationId { get; set; }
public string Name { get; set; }
public virtual ICollection<TestAccount> TestAccounts { get; set; }
}
public class TestAccount
{
public int TestAccountId { get; set; }
public int ApplicationId { get; set; }
public string Name { get; set; }
public virtual Application Application { get; set; }
}
EF Mapping looks like this:
modelBuilder.Entity<Application>()
.HasMany(a => a.TestAccounts)
.WithRequired(t => t.Application)
.WillCascadeOnDelete(false);
In one part of my code I want to retrieve data for Application and have
it return TestAccount data.
In another part of my code I want to retrieve data for Application and
have it NOT return TestAccount data.
Is there a way I can make this happen with LINQ or some other way?
This question has already been answered here: Disable lazy loading by default in Entity Framework 4.
Basically, in the constructor of your DbContext, just add this:
this.Configuration.LazyLoadingEnabled = false;
I hope this helps.
EDIT
Also, if you want to know how to load it manually later, it should be a simple matter of using Include() like this:
var query = context.Application.Include(x => x.TestAccounts).ToList()
Sorry about the title; couldn't think of a better one.
Any way, I'm accessing an associated property in my view like so:
#Model.Company.CompanyName // No problems here...
The model is a viewmodel mapped to an EF POCO. The Model has several properties associated to the Company table. Only one of the properties in the model share the same name as the PK in the Company table. All the other properties reference the same table:
public class MyModelClass
{
public int Id { get; set; }
public int CompanyId { get; set; }
public int AnotherCompanyId { get; set; } // References CompanyId
public int AndAnotherCompanyId { get; set; } // References CompanyId
public Company Company { get; set; }
}
public class Company
{
public int CompanyId { get; set; }
public string CompanyName { get; set; }
public string Address { get; set; }
}
I'm obviously missing something here.
How can I get the names of the other companies in my Model?
Any help is greatly appreciated.
The model is a viewmodel mapped to an EF POCO
I think you are confusing the notion of a view model. A view model is a class that is specifically designed to meet the requirements of your view. So if in your view you need to display the company name and not the company id then your view model should directly contain a CompanyName property. Or a reference to another view model (CompanyViewModel) which contains the name directly. It is then the responsibility of your controller action to query your domain models (EF entities) and aggregate them into a single view model tat will contain all the necessary information that the view requires.
Here's how a typical view model might look like:
public class MyViewModel
{
public CompanyViewModel Company { get; set; }
public CompanyViewModel AnotherCompany { get; set; }
public CompanyViewModel AndAnotherCompany { get; set; }
}
public class CompanyViewModel
{
public string Name { get; set; }
}
Where the data comes from in this view model is not important. You could have the Company property populated from your EF stuff, the AnotherCompany property populated from a XML file and AndAnotherCompany from WCF.
I am trying to define two many to many relationship to same object using fluent api.
Here is the simplified model:
public class PurchaseRequisition
{
[Key, ForeignKey("Transaction")]
public int TransactionId { get; set; }
public virtual ICollection<People> RequisitionedBys { get; set; }
public virtual ICollection<People> AuthorizedSignatures { get; set; }
}
public class People
{
[Key]
public string Id{ get; set; }
public string FullName { get; set; }
public virtual ICollection<PurchaseRequisition> PurchaseRequisitionsForRequisitionedBys { get; set; }
public virtual ICollection<PurchaseRequisition> PurchaseRequisitionsForAuthorizedSignatures { get; set; }
}
Here is the fluent api code:
modelBuilder.Entity<PurchaseRequisition>()
.HasMany(a => a.RequisitionedBys)
.WithMany(b => b.PurchaseRequisitionsForRequisitionedBys)
.Map(x =>
{
x.MapLeftKey("PurchaseRequisitionId");
x.MapRightKey("RequisitionedById");
x.ToTable("PurchaseRequisitionRequisitionedBy");
});
modelBuilder.Entity<PurchaseRequisition>()
.HasMany(a => a.AuthorizedSignatures)
.WithMany(b =>b.PurchaseRequisitionsForAuthorizedSignatures)
.Map(x =>
{
x.MapLeftKey("PurchaseRequisitionId");
x.MapRightKey("AuthorizedSignatureId");
x.ToTable("PurchaseRequisitionAuthorizedSignature");
});
What I want is to generate two separate linking tables, but what EF generates is two foreign key columns to PurchaseRequisition in People table and 1 foreign key column to People in PurchaseRequisition field.
Can anyone tell me what might be wrong?
The problem was fixed.
I mistakenly thought that my database initializer code would drop and recreate the database since I made changes to the model classes and my Initializer class extended DropCreateDatabaseIfModelChanges.
As Slauma suggested, fluent api code was not being reached even though the model has been changed. I was setting the initializer using SetInitializer() method and this code only ran when I used a context instance for the first time to access the DB.
I am not sure if this is possible but here is my situation.
Say I have a model like this:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
My View model looks like this:
public class ProductModel
{
public int Id { get; set; }
public string Name { get; set; }
public string CustomViewProperty { get; set; }
}
I am using my ProductModel to post back to a form and I don't care or need the Custom View Property. This mapping works fine as automapper drops the unknown properties.
What I would like to do is map my custom properties in only one direction. i.e.
Mapper.CreateMap<Product, ProductModel>()
.ForMember(dest => dest.CustomViewProperty //???This is where I am stuck
What ends up happening is when I call "ToModel", automapper dumps my unknown properties and nothing comes over the wire.
Like this.
var product = _productService.GetProduct();
var model = product.ToModel;
model.CustomViewProperty = "Hello World"; //This doesn't go over the wire
return View(model);
Is this possible? Thanks.
You should ignore unmapped properties:
Mapper.CreateMap<Product, ProductModel>()
.ForMember(dest => dest.CustomViewProperty, opt=>opt.Ignore());
or map them:
Mapper.CreateMap<Product, ProductModel>()
.ForMember(dest => dest.CustomViewProperty, opt=>opt.MapFrom(product=>"Hello world"));