How to use automapper in one to many relationship? - c#

I have a method that creates a Product. Im getting such a user and checking if he is not null. If there is a user I'm creating new Product. I want to create Product, transfer this product to product dto and add this to the User Product List and return new ProductDto array within product name, product price and the name and Id of the user who created it. But when I try to add the product I'm getting error that I can't convert productDto to product. What I am doing wrong? Any suggestions?
public async Task<ActionResult<ProductDto>> AddProduct(string username, ProductDto productDto)
{
User user= await _context.Users
.Where(x=>x.UserName==username)
.Include(x => x.Products)
.SingleOrDefaultAsync();
if(user!= null)
{
var product = new Product
{
Name = productDto.Name,
Price = productDto.Price,
UserId = u.Id
};
var productToReturn = _mapper.Map<Product, ProductDto>(product);
user.Products.Add(productToReturn); // Cannot convert productDto to product
await _context.SaveChangesAsync();
}
return Ok(productToReturn);
public class ProductDto
{
public string Name { get; set; }
public string Username { get; set; }
public decimal Price { get; set; }
}
public class Product : BaseEntity
{
public string Name { get; set; }
public decimal Price { get; set; }
public User User { get; set; }
public int UserId { get; set; }
}
public class User : BaseEntity
{
public string UserName { get; set; }
public byte[] PasswordHash { get; set; }
public byte[] PasswordSalt { get; set; }
public List<Product> Products { get; set; }
}
I also configured automapper profiles
public AutoMapperProfiles()
{
CreateMap<Product, ProductDto>().ForMember(dest => dest.Username, opt =>
opt.MapFrom(src => src.User.UserName));
}

Seems the issue could be, you are trying to add ProductDto to the user.Products list. But it's looking for an object Product. try to modify code as,
if(user!= null)
{
var product = new Product
{
Name = productDto.Name,
Price = productDto.Price,
UserId = u.Id
};
user.Products.Add(product);
await _context.SaveChangesAsync();
var productToReturn = _mapper.Map<Product, ProductDto>(product);
return Ok(productToReturn);
}

Related

Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException

I'm trying to update an entity but when I do saveChangesAsync() I'm getting this error message:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded.
Before updating, I'm getting this entity like this in my repository class:
public IQueryable<Profile> getProfileByUserEmail(string userEmail)
{
return _context.Profiles
.Include(pr => pr.Photos)
.AsQueryable()
.Where(p => p.UserEmail == userEmail);
}
In my service, I call my repository class and materialize the query with the FirstOrDefaulAsync() like this:
public async Task<Profile> getProfileByUserEmail(string userEmail)
=> await _repository.getProfileByUserEmail(userEmail).FirstOrDefaultAsync();
In my controller I get the profile, add a new photo and update it, like this:
var profile = await _service.getProfileByUserEmail(userEmail: model.UserEmail);
Photo newPhoto = new Photo();
newPhoto.Description = model.Description;
newPhoto.Id = Guid.NewGuid();
profile.Photos.Add(newPhoto);
var updated = await _service.update(profile);
This is my Profile entity:
public class Profile
{
[Key]
public Guid Id { get; set; }
public DateTime CreationDate { get; set; }
public string ProfilePhoto { get; set; }
public HashSet<Photo> Photos { get; set; }
}
and my Photo entity:
public class Photo
{
[Key]
public Guid Id { get; set; }
public string Uri { get; set; }
[StringLength(1000)]
public string Description { get; set; }
public Guid ProfileId { get; set; }
public virtual Profile Profile { get; set; }
}
I have found on the internet the reason but not a possible solution, if anybody could help that would be great, thanks in advance.

Cant get related data with Linq/lambda. Public virtual class not filled out

My model:
public partial class Books
{
public int Id { get; set; }
public string Grade { get; set; }
public string Goal { get; set; }
public int? Subjectid { get; set; }
public DateTime? Createdon { get; set; }
//public string Subjectname { get; set; }
//public virtual ICollection<Subjects> RelatedSubject { get; set; }
public virtual Subjects Subject { get; set; }
My controller: (webapi)
public async Task<ActionResult<IEnumerable<Books>>> GetBooks()
{
var Books = await _context.Books
.Join(_context.Subjects,
c => c.Subjectid,
s => s.Id,
(c, s) => new Subjects {Id = s.Id, Name = s.Name, })
//(c, s) => new { subjectname = s.Subject, subjectid = s.Id })
.ToListAsync();
return Books;
//return await _context.Books.ToListAsync();
}
I get this error:
Error CS0029 Cannot implicitly convert type
'System.Collections.Generic.List<myProject.Subjects>' to
'Microsoft.AspNetCore.Mvc.ActionResult<System.Collections.Generic.IEnumerable<myProject.Books>>'
WHen I call the api I see the Subject is a empty property.
Why wont it get the Subject name from the linq/lambda?
EDIT
After some tips I tried this:
var Books = _context.Books.Include("Subjects.Name").ToListAsync();
InvalidOperationException: Invalid include path: 'Subjects.Name' - couldn't find navigation for: 'Subjects'
Is it a relationship I should have added? I have database-first migrated.
The virtual keyword is only needed when using lazy-loading. Then the Property is loaded when first accessed (not when reading data from database).
When you want to load data from DB including some properties you may need to use .Include() extension
The code may look like:
public async Task<ActionResult<IEnumerable<Books>>> GetBooks()
{
var books = await _context.Books.Include(nameof(Books.Subject)).ToListAsync();
return books;
}

How to get One Table data on the basis of ID from another table

I have created 3 tables relation (users, projects, products)
one user has many projects and one project has many products (one to many)
I need to show all the projects and contained products on user login
I have done it using the following code but I don't think this is best way to deal with it. I need to do it better
public ActionResult Index()
{
ModulesViewModel mvm = new ModulesViewModel();
List<Modules> modules = new List<Modules>();
var userId = User.Identity.GetUserId();
var projects = _adsDbContext.Project.Where(x=>x.UserID == userId).ToList();
foreach (var pro in projects)
{
var productData = _adsDbContext.Product.Where(x => x.ProjectID == pro.ProjectID);
modules.AddRange(productData);
}
modules = modules.OrderBy(x => x.ProjectID).OrderBy(x=>x.ModuleNumber).ToList();
mvm.Modules = modules;
return View(mvm);
}
public class Project
{
public int ProjectID { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductData> Products { get; set; }
public string UserID { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
}
public class ProductData : Modules
{
public int ProductDataID { get; set; }
public float ConversionRate { get; set; }
public float Price { get; set; }
public float TotalSales { get; set; }
public float GrossSales { get; set; }
public float NetProfit { get; set; }
public float ProfitPerLead { get; set; }
}
public abstract class Modules
{
public int ProjectID { get; set; }
public virtual Project Project { get; set; }
}
This works fine but I need to do it in better way rather to create relation from scratch or make the query better.
Your model contains navigation property for each end of the project to product one-to-many relationship.
This allows you to start the query from the project, apply the filter and then "navigate" down using the collection navigation property and SelectMany:
var modules = _adsDbContext.Project
.Where(x => x.UserID == userId)
.SelectMany(x => x.Products) // <--
.OrderBy(x => x.ProjectID).ThenBy(x => x.ModuleNumber)
.ToList<Modules>();
or you can start the query from the product and use the reference navigation property to "navigate" up for applying the filter:
var modules = _adsDbContext.Product
.Where(x => x.Project.UserID == userId) // <--
.OrderBy(x => x.ProjectID).ThenBy(x => x.ModuleNumber)
.ToList<Modules>();

EF How to make in one request

I have the following architecture:
Standard ASP.NET Identity tables, user can have one and only one role
There are 2 roles : Driver and Manager
User has driver or manager role. Every driver has record in Driver table, Every manager has record in Manager table.
Both, Manager and Driver tables, has FirstName and LastName
My entities and map among them (0..1:1):
public class AspNetUser
{
//.....
public virtual Manager Manager { get; set; }
public virtual Driver Driver { get; set; }
}
public partial class Driver
{
public int Id { get; set; }
//....
public virtual AspNetUser AspNetUser { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Manager
{
public int Id { get; set; }
//....
public virtual AspNetUser AspNetUser { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Nickname { get; set; }
}
public class AspNetUserMap : EntityTypeConfiguration<AspNetUser>
{
public AspNetUserMap()
{
this.HasKey(dp => dp.Id);
this.Property(p => p.UserName).IsRequired().HasMaxLength(256);
this.HasIndex(p => p.UserName).IsClustered(false).IsUnique();
this.HasOptional(c => c.Driver)
.WithOptionalPrincipal(a => a.AspNetUser)
.Map(m => m.MapKey("AspNetUserId"));
this.HasOptional(c => c.Manager)
.WithOptionalPrincipal(a => a.AspNetUser)
.Map(m => m.MapKey("AspNetUserId"));
Now I need to get all users from AspNetUser, but with their FN/LN. To get FN/LN I should check role of user and then call Driver or Manager.
I have the following code:
List<ChatUserDomain> returnedUsers = new List<ChatUserDomain>();
var users = (from i in db.AspNetUsers
.Include(path => path.Driver)
.Include(path => path.Manager)
.Include(path => path.AspNetRoles)
where i.CompanyId == db.AspNetUsers.Where(p => p.UserName.Equals(username, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault().CompanyId
select new
{
AspNetUser = i,
Username = i.UserName,
....
}
).ToList();
foreach (var user in users)
{
returnedUsers.Add(new ChatUserDomain()
{
UserId = user.AspNetUser.Id,
Name = GetNameByUser(user.AspNetUser),
...
});
}
return returnedUsers;
and GetNameByUser method:
private string GetNameByUser(AspNetUser aspNetUser)
{
IProfile profile = GetProfileByUser(aspNetUser);
if (!String.IsNullOrWhiteSpace(profile.FirstName) && !String.IsNullOrEmpty(profile.LastName))
{
return (GetFirstLettersOfFullName(profile.FirstName, profile.LastName));
}
else
{
return (aspNetUser.UserName.Substring(0, 2));
}
}
private IProfile GetProfileByUser(AspNetUser aspNetUser)
{
IProfile profile = null;
var role = aspNetUser.AspNetRoles.FirstOrDefault().Name;
if (role.Equals(Domain.StaticStrings.RoleStaticStrings.ROLE_DRIVER))
profile = aspNetUser.Driver;
else
profile = aspNetUser.Manager;
return profile;
}
It works, but it creates additional user.Count calls to DB. How to say to EF "load Manager and Driver data in the parent request"?

How to populate my base Customer with EF

see my class structure first.
public class CustomerBase
{
public int CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
}
public class Customer : CustomerBase
{
public virtual List<Addresses> Addresses { get; set; }
}
public class Addresses
{
[Key]
public int AddressID { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public bool IsDefault { get; set; }
public virtual List<Contacts> Contacts { get; set; }
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
}
public class Contacts
{
[Key]
public int ContactID { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
public bool IsDefault { get; set; }
public int AddressID { get; set; }
public virtual Addresses Customer { get; set; }
}
public class TestDBContext : DbContext
{
public TestDBContext()
: base("name=TestDBContext")
{
}
public DbSet<Customer> Customer { get; set; }
public DbSet<Addresses> Addresses { get; set; }
public DbSet<Contacts> Contacts { get; set; }
}
now this way i am trying to populate my customer base but getting error.
var bsCustomer1 = (from c in db.Customer
where (c.CustomerID == 2)
select new
{
CustomerID = c.CustomerID,
FirstName = c.FirstName,
LastName = c.LastName,
Addresses = (from ad in c.Addresses
where (ad.IsDefault == true)
from cts in ad.Contacts
where (cts != null && cts.IsDefault == true)
select ad).ToList(),
}).ToList()
.Select(x => new CustomerBase
{
CustomerID = x.CustomerID,
FirstName = x.FirstName,
LastName = x.LastName,
Address1 = x.Addresses.Select(a => a.Address1).SingleOrDefault(),
Address2 = x.Addresses.Select(a => a.Address2).SingleOrDefault(),
Phone = x.Addresses.Select(c => c.Contacts.Select(cd => cd.Phone).SingleOrDefault()),
Fax = x.Addresses.Select(c => c.Contacts.Select(cd => cd.Fax).SingleOrDefault())
}).ToList();
as per my situation a single customer may have multiple address but there should be one default one which i am pulling. a single address may have multiple contacts details but there should be one default one which i am pulling.
address1,address2, Phone and Fax are in base customer class. i want to pull single data from address and contacts tables based on isdefault is true and populate my customer. i am not very good in linq. so not being able to compose the query. please help me to compose it. thanks
Try the code below, guess it may fit about your request.
var bsCustomer1 = db.Customer.Where(p => p.CustomerID == 2)
.Select(x => new CustomerBase
{
CustomerID = x.CustomerID,
FirstName = x.FirstName,
LastName = x.LastName,
Address1 = x.Addresses.First(a => a.IsDefault).Address1,
Address2 = x.Addresses.First(a => a.IsDefault).Address2,
Phone = x.Addresses.First(a => a.IsDefault).Contacts.First(c => c.IsDefault).Phone),
Fax = x.Addresses.First(a => a.IsDefault).Contacts.First(c => c.IsDefault).Fax)
}).ToList();
Without knowing your actual meaning when you say: "i want to pull single data from address and contacts tables based on isdefault is true and populate my customer" that could mean two things:
I want to project a new object
I want to UPDATE the backing database.
Okay a few things about EF:
You have a context for CRUD (Create, Retrieve, Update, Delete) statements to the database.
The Context knows all the objects you identified in the database when setting up the EF file.
The t4 templates are created for entity context and the entity name itself and generate the context reference in the previous steps as well as create POCO class objects.
To create NEW objects you don't have to reference the object above it or below it. You just need to create it and then update the database with it.
So for an example of EF let's say I have two database tables:
I have a table tePerson that has fields: PersonId, FirstName, LastName, OrderId. This table has values
1 Brett X 1
2 Emily X 2
4 Ryan Y 1
10 Mark Z 1
OrderId is a foreign Key to a table teOrder with only has two fields: OrderId and Description.
1 Shirt
2 Dress
And my POCO objects generated from the T4 are:
public partial class tePerson
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Nullable<int> OrderId { get; set; }
public virtual teOrder teOrder { get; set; }
}
It is important to note that that 'virtual teOrder' points to another POCO for me like this:
public partial class teOrder
{
public teOrder()
{
this.tePersons = new HashSet<tePerson>();
}
public int OrderId { get; set; }
public string Description { get; set; }
public virtual ICollection<tePerson> tePersons { get; set; }
}
Example for just projecting and for updating the database from the context as well as updating the database below. The key thing to remember is that objects when doing 'selects' with EF are not realized till you do a method like 'ToList()' to make them concrete. Else they are context db set which you cannot chain off of.
public class OtherPerson
{
public int PersonId { get; set; }
public string PersonLongName { get; set; }
public teOrder Order { get; set; }
}
static void Main(string[] args)
{
using (var context = new TesterEntities())
{
//Say I just want to project a new object with a select starting from orders and then traversing up. Not too hard
var newObjects = context.teOrders.Where(order => order.OrderId == 1)
//SelectMan will FLATTEN a list off of a parent or child in a one to many relationship
.SelectMany(peopleInOrderOne => peopleInOrderOne.tePersons)
.ToList()
.Select(existingPerson => new OtherPerson
{
PersonId = existingPerson.PersonId,
PersonLongName = $"{existingPerson.FirstName} {existingPerson.LastName}",
Order = existingPerson.teOrder
})
.ToList();
newObjects.ForEach(newPerson => Console.WriteLine($"{newPerson.PersonId} {newPerson.PersonLongName} {newPerson.Order.Description}"));
// Just an action clause to repeat find items in my context, the important thing to note is that y extends teOrder which is another POCO inside my POCO
Action<string, List<tePerson>> GetOrdersForPeople = (header, people) =>
{
Console.WriteLine(header);
people.ForEach(person => Console.WriteLine($"{person.FirstName} {person.LastName} {person.teOrder.Description}"));
Console.WriteLine();
};
//I want to look at a person and their orders. I don't have to do multiple selects down, lazy loading by default gives me a child object off of EF
GetOrdersForPeople("First Run", context.tePersons.ToList());
//Say I want a new order for a set of persons in my list?
var newOrder = new teOrder { Description = "Shoes" };
context.teOrders.Add(newOrder);
context.SaveChanges();
//Now I want to add the new order
context.tePersons.SingleOrDefault(person => person.PersonId == 1).teOrder = newOrder;
context.SaveChanges();
//I want to rexamine now
GetOrdersForPeople("After changes", context.tePersons.ToList());
//My newOrder is in memory and I can alter it like clay still and the database will know if I change the context
newOrder.Description = "Athletic Shoes";
context.SaveChanges();
GetOrdersForPeople("After changes 2", context.tePersons.ToList());
//Say I want to update a few people with new orders at the same time
var peopleBesidesFirst = context.tePersons.Where(person => person.PersonId != 1).ToList();
var firstPersonInList = context.tePersons.Where(person => person.PersonId == 1).ToList();
var newOrders = new List<teOrder> {
new teOrder { Description = "Hat", tePersons = peopleBesidesFirst },
new teOrder { Description = "Tie", tePersons = firstPersonInList }
};
context.teOrders.AddRange(newOrders);
context.SaveChanges();
GetOrdersForPeople("After changes 3", context.tePersons.ToList());
}
Console.ReadLine();
}

Categories