Read Data from table with no relationship - c#

I have some global information (Company name, street, phone, …) stored in a table "CompInfo".
This table has only one entry and has no relationship to other tables.
Now I need this information in a view to generate, i.e. offers.
I tried to add this data in the controller, but I don't know how I can send this info to my view.
Here is my controller:
public async Task<IActionResult> Offer(int qry_offer)
{
ViewData["Message"] = "Offer";
var x412Context = _context.Offer
.Where(m => m.Id == qry_offer);
x412Context = x412Context
.Include(o => o.Kunde)
.Include(o => o.Mandant)
.Include(m => m.OfferPos)
.ThenInclude(m => m.Ust)
;
return View(await x412Context.ToListAsync());
}
First I add the following code
var Comp_Info = _context.CompInfo
.Where(m => m.Id == 1);
With breakpoints and debugging I see that Comp_Info has the information I require.
But I don´t know how to get this info to my view.
I tried
ViewData["street"] = Comp_Info.street;
But this doesn't work.
Does anyone have an idea how I can transmit the data to the view?

You can return a viewmodel in your Offer method that looks like this :
public class FooVM
{
public List<Offer> Offers {get; set;}
public CompInfo CompInfo{get; set;}
}
and in your controller initilize the FooVm like this:
var vm = new FooVM
{
Offers = await x412Context.ToListAsync(),
CompInfo = Comp_Info
};
and then in your Offer Method you return View(vm);
then your Offer.cshtml will look something like :
#model FooVm;
// some html code
#foreach (var offer in Model.Offers)
{
// Do something
}
<span>#Model.CompInfo.Street</span>

you can implement another class which should have members as the list (of the same type x412Context.ToListAsync()) and another member which will have the same datatype as Comp_Info.street.
then you can return the same class object to the view like return View(newClassObj);, you can access it as we do to access model class members in view.

Related

AutoMapper add custom property that does not exist in source but has to exist in target

I've got a simple ASP.NET Core controller:
[HttpGet("{id:int}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
[ProducesResponseType(500)]
public ActionResult<RequestDto> Get(int id, [FromQuery]GetRequest getRequest)
{
var query = Mapper.Map<GetRequest, FindRequestQuery>(getRequest);
query.Id = id;
var request = _requestService.Find(query);
if (request == null)
{
return NotFound();
}
var requestDto = Mapper.Map<Request, RequestDto>(request);
return Ok(requestDto);
}
My GetRequest does not have Id property, however my FindRequestQuery has one.
The example above works just fine, but I'm interested to know if there's a way to tell AutoMapper to map id from method parameter to FindRequestQuery property?
Have you tried Custom Value Resolvers?
You are looking for the capability provided by AutoMapper's Inline Mapping functionality.
Specifically, it will look something like this
var query = Mapper.Map<GetRequest, FindRequestQuery>(
getRequest,
opt => opt.ConfigureMap().ForMember(dest => dest.Id, m => m.MapFrom(src => id)));

.OrderBy() / .OrderByDescending() with .FirstOrDefault()/.First()

I have list of results from table named "product".
Id User ProductName ArrivalDate
---- ----- ------------ ------------
1 James Shoes 05/07/2016
2 Jenny TShirt 05/01/2018
3 James Pants 13/05/2017
i would like to sort the result by descending order based on ArrivalDate where User is "James", which mean it should return to me the third row result.
However, if i do as below:
return List(spec).OrderByDescending(x=>x.ArrivalDate).FirstOrDefault();
the result i got is still the first one, appereciate if anyone could guide on this.
Below is the sample code:
public class EfRepository<T> : IRepository<T>, IAsyncRepository<T> where T : BaseEntity
{
public T GetSingleBySpec(ISpecification<T> spec)
{
return List(spec).FirstOrDefault();
}
}
public class ProductSpecification : BaseSpecification<NewProducts>
{
public ProductSpecification(string User)
: base(b => b.User == User) //User = James
{
}
}
public class ProductService : IProductService
{
public void getOrderProductDetails
{
var data = _productRepository.GetSingleBySpec(new ProductSpecification(user));
}
}
I don't see a filter for the user and you are ordering by user. Try this.
return List(spec).Where(x => x.User == "James")
.OrderByDescending(y => y.ArrivalDate)
.FirstOrDefault();
If you want to get just a specific user last arrival time you should add extra condition to where clause
FirstOrDefault generates a query like this:
Select top 1 ....
It seems to me that you are trying to encapsulate some basic Linq functionality using your own methods, but you are only complicating things, at the same time you are making it limited and hard to use, instead of flexible and easy to use.
This is what I would do instead:
public void getOrderProductDetails
{
var data = _productRepository
.Where(x => x.User == user)
.OrderByDescending(x => x.ArrivalDate)
.FirstOrDefault();
// process data ...
}
You can also put the "filter" ~inside the FirstOrDefault method.
I show a generic example (both ways) below.
Also note the string-compare for case insensitivity.
ICollection<Employee> employees = (blah blah blah);
Employee firstEmployeeWhereClause = employees.Where(z => z.LastName.Equals("Smith", StringComparison.OrdinalIgnoreCase)).OrderByDescending(x => x.EmployeeSurrogateKey).FirstOrDefault();
Employee secondEmployeeNoWhereClause = employees.OrderByDescending(x => x.EmployeeSurrogateKey).FirstOrDefault(z => z.LastName.Equals("Smith", StringComparison.OrdinalIgnoreCase));

Entity framework - calculate sum field from child records

I'm using entity framework core with ASP.NET Core, code first.
In my app I have invoices, with the typical InvoiceHeader -> InvoiceLine relationship. The InvoiceLine entities have a LineAmount field, which I want to sum and display on the InvoiceHeader when displayed as a list (so I can see the invoice total when viewing the list of invoices).
I'm guessing I'll need to add a TotalAmount property to the InvoiceHeader entity, with the annotation [NotMapped]. But how to most efficiently populate it?
At the moment my InvoiceHeaderController.Index() is:
// GET: InvoiceHeaders
public async Task<IActionResult> Index()
{
ApplicationUser appUser = ConstantData.GetApplicationUser(_context, _userManager.GetUserId(User));
var applicationDbContext = _context.InvoiceHeader.Include(i => i.Customer).Include(i => i.CustomerBranch)
.Where(i => i.CustomerID == appUser.CustomerID);
return View(await applicationDbContext.ToListAsync());
}
Can anyone tell me what the most efficient way is to calculate (sum) this TotalAmount property?
Thanks.
Selecting sum as separate field you need to create new model class as shown below
public class InvoiceHeaderModel{
public InvoiceHeader invoiceHeader{get;set;}
public decimal TotalAmount {get;set;}
}
and make change in action as
// GET: InvoiceHeaders
public async Task<IActionResult> Index()
{
ApplicationUser appUser = ConstantData.GetApplicationUser(_context, _userManager.GetUserId(User));
var applicationDbContext =await _context.InvoiceHeader.Where(i =>i.CustomerID == appUser.CustomerID).ToListAsync();
var data = applicationDbContext.Aggregate( new List<InvoiceHeaderModel>(),(invoiceHeaderModellist, it)=>{ invoiceHeaderModellist.Add(new InvoiceHeaderModel(){ InvoiceHeader =it,TotalAmount = it.InvoiceLine.Sum(t=>t.LineAmount)}); return invoiceHeaderModellist;});
return View(data);
}
In this action i don't think you required to include 'Include(i => i.Customer).Include(i => i.CustomerBranch)' if required you can add before where closure.
I managed to work it out. Saneesh's suggestion was close, but not quite what I wanted.
The code I ended up using is:
// GET: InvoiceHeaders
public async Task<IActionResult> Index()
{
ApplicationUser appUser = ConstantData.GetApplicationUser(_context, _userManager.GetUserId(User));
var applicationDbContext = _context.InvoiceHeader.Include(i => i.Customer).Include(i => i.CustomerBranch)
.Where(i => i.CustomerID == appUser.CustomerID)
.Select(i => new InvoiceListViewModel
{
invoiceHeader = i,
TotalAmount = i.InvoiceLines.Sum(t => t.LineAmount)
});
return View(await applicationDbContext.ToListAsync());
}
Thanks for your help Saneesh.

Not able to add second entity to ViewModel with Automapper?

I'm still learning MVC and I'm was just recently suggested to look at using AutoMapper to map my ViewModel. So, I started a test but I'm not understanding how to map two entities nor can I find an example.
What I have is a Person table and an Address table. I want to combine the two into a ViewModel to pass to my View. Looking at examples they always show how to map one entity but not two.
In the following code I can see that both my currentPerson and currentAddress objects are populated with data using the debugger but then my currentMember ViewModel only has the Person data. That makes sense because I'm only using Mapper.Map on the Person but how do I tell the currentMember to map the Address as well?
public ActionResult Edit(int id = 0)
{
using (DataContext db = new DataContext())
{
Person currentPerson = db.Person.Find(id);
Address currentAddress = db.Address.Single(a => a.PID == id);
AutoMapper.Mapper.CreateMap<Person, AdminViewModel>();
AutoMapper.Mapper.CreateMap<Address, AdminViewModel>();
MemberViewModel currentMember = AutoMapper.Mapper.Map<AdminViewModel>(currentPerson);
return View(currentMember);
}
}
AutoMapper.Map has an overload that takes a source and destination object. So you can take advantage of it using something like this:
Create ViewModels
public class PersonViewModel
{
//your person fields here..
//create them with the same name as Person
//to avoid having to set each field during mapping
}
public class AddressViewModel
{
//your address fields here..
}
public class AdminViewModel
{
public PersonViewModel Person {get; set;}
public AddressViewModel Address {get; set;}
}
Crete the mappings (You should centralize all your mappings in a class):
public class EntityToViewModelProfile : Profile
{
protected override void Configure()
{
//Create mappings for Person and Address
AutoMapper.Mapper.CreateMap<Person, AdminViewModel>();
AutoMapper.Mapper.CreateMap<Address, AdminViewModel>();
//Create a map to AdminViewModel for both Person and Address
Mapper.CreateMap<Models.Person, Models.AdminViewModel>()
.ForMember(dest => dest.Person, opt => opt.MapFrom(src => src));
Mapper.CreateMap<Models.Address, Models.AdminViewModel>()
.ForMember(dest => dest.Address, opt => opt.MapFrom(src => src));
}
}
Register EntityToViewModelProfile in Global.asax.cs
AutoMapper.Mapper.Initialize(x =>
{
x.AddProfile<EntityToViewModelProfile>();
});
Then, in your controller you use the overload of .Map
using (DataContext db = new DataContext())
{
Person currentPerson = db.Person.Find(id);
Address currentAddress = db.Address.Single(a => a.PID == id);
var mapPerson = AutoMapper.Mapper.Map<Person, AdminViewModel>(currentPerson);
var mapPersonAndAddress = AutoMapper.Mapper.Map<Address, AdminViewModel>(currentAddress, mapPerson);
return View(mapPersonAndAddress);
}
Answer from Jpgrassi looks good and it's pretty cool way to convert. But If you are doing the conversions in a DB or BL layer (rather than the Controller itself), keeping your conversions to Business Objects to BOViewModel (Person to PersonViewModel etc.) in a is more reusable. More often you need a separate project which defines your BOViewModels (Something like MyProject.BO)
I would rather follow the simple and straightforward approach to create the AdminViewModel,
var personViewModel = AutoMapper.Mapper.CreateMap<Person, PersonViewModel>();
var addressViewModel = AutoMapper.Mapper.CreateMap<Address, AddressViewModel>();
These conversions are useful to convert DB objects to BO, which would ideally takes place in a DB or BL layer. Then pass the personViewModel and addressViewModel to Controller and create the AdminViewModel in the controller.
This is simple and this pattern works across the service boundaries or applications with many layers.

Entity framework, problems updating related objects

I am currently working on a project using the latest version of Entity Framework and I have come across an issue which I can not seem to solve.
When it comes to updating existing objects, I can fairly easily update the object properties ok, until it comes to a property which is a reference to another class.
In the below example I have a class called Foo, which stores various properties, with 2 of these being instances of other classes
public class Foo
{
public int Id {get; set;}
public string Name {get; set;}
public SubFoo SubFoo {get; set}
public AnotherSubFoo AnotherSubFoo {get; set}
}
When I use the below Edit() method, I pass in the object I wish to update and I can manage to get the Name to properly update, however I have not managed to find a way in which to get the properties of the SubFoo to change. For example, if the SubFoo class has a property of Name, and this has been changed and is different between my DB and the newFoo, it does not get updated.
public Foo Edit(Foo newFoo)
{
var dbFoo = context.Foo
.Include(x => x.SubFoo)
.Include(x => x.AnotherSubFoo)
.Single(c => c.Id == newFoo.Id);
var entry = context.Entry<Foo>(dbFoo);
entry.OriginalValues.SetValues(dbFoo);
entry.CurrentValues.SetValues(newFoo);
context.SaveChanges();
return newFoo;
}
Any help or pointers would be greatly appreciated.
UPDATE:
Based on the comment by Slauma I have modified my method to
public Foo Edit(Foo newFoo)
{
var dbFoo = context.Foo
.Include(x => x.SubFoo)
.Include(x => x.AnotherSubFoo)
.Single(c => c.Id == newFoo.Id);
context.Entry(dbFoo).CurrentValues.SetValues(newFoo);
context.Entry(dbFoo.SubFoo).CurrentValues.SetValues(newFoo.SubFoo);
context.SaveChanges();
return newFoo;
}
When running this now, I get the error:
The entity type Collection`1 is not part of the model for the current
context.
To try and get around this, I added code to try to attach the newFoo subclasses to the context, but this through an error saying that the ObjectManager already had an entity the same:
An object with the same key already exists in the ObjectStateManager.
The ObjectStateManager cannot track multiple objects with the same
key
CurrentValues.SetValues only updates scalar properties but no related entities, so you must do the same for each related entity:
public Foo Edit(Foo newFoo)
{
var dbFoo = context.Foo
.Include(x => x.SubFoo)
.Include(x => x.AnotherSubFoo)
.Single(c => c.Id == newFoo.Id);
context.Entry(dbFoo).CurrentValues.SetValues(newFoo);
context.Entry(dbFoo.SubFoo).CurrentValues.SetValues(newFoo.SubFoo);
context.Entry(dbFoo.AnotherSubFoo).CurrentValues.SetValues(newFoo.AnotherSubFoo);
context.SaveChanges();
return newFoo;
}
If the relationship could have been removed altogether or have been created you also need to handle those cases explicitly:
public Foo Edit(Foo newFoo)
{
var dbFoo = context.Foo
.Include(x => x.SubFoo)
.Include(x => x.AnotherSubFoo)
.Single(c => c.Id == newFoo.Id);
context.Entry(dbFoo).CurrentValues.SetValues(newFoo);
if (dbFoo.SubFoo != null)
{
if (newFoo.SubFoo != null)
{
if (dbFoo.SubFoo.Id == newFoo.SubFoo.Id)
// no relationship change, only scalar prop.
context.Entry(dbFoo.SubFoo).CurrentValues.SetValues(newFoo.SubFoo);
else
{
// Relationship change
// Attach assumes that newFoo.SubFoo is an existing entity
context.SubFoos.Attach(newFoo.SubFoo);
dbFoo.SubFoo = newFoo.SubFoo;
}
}
else // relationship has been removed
dbFoo.SubFoo = null;
}
else
{
if (newFoo.SubFoo != null) // relationship has been added
{
// Attach assumes that newFoo.SubFoo is an existing entity
context.SubFoos.Attach(newFoo.SubFoo);
dbFoo.SubFoo = newFoo.SubFoo;
}
// else -> old and new SubFoo is null -> nothing to do
}
// the same logic for AnotherSubFoo ...
context.SaveChanges();
return newFoo;
}
You eventually also need to set the state of the attached entities to Modified if the relationship has been changed and the scalar properties as well.
Edit
If - according to your comment - Foo.SubFoo is actually a collection and not only a reference you will need something like this to update the related entities:
public Foo Edit(Foo newFoo)
{
var dbFoo = context.Foo
.Include(x => x.SubFoo)
.Include(x => x.AnotherSubFoo)
.Single(c => c.Id == newFoo.Id);
// Update foo (works only for scalar properties)
context.Entry(dbFoo).CurrentValues.SetValues(newFoo);
// Delete subFoos from database that are not in the newFoo.SubFoo collection
foreach (var dbSubFoo in dbFoo.SubFoo.ToList())
if (!newFoo.SubFoo.Any(s => s.Id == dbSubFoo.Id))
context.SubFoos.Remove(dbSubFoo);
foreach (var newSubFoo in newFoo.SubFoo)
{
var dbSubFoo = dbFoo.SubFoo.SingleOrDefault(s => s.Id == newSubFoo.Id);
if (dbSubFoo != null)
// Update subFoos that are in the newFoo.SubFoo collection
context.Entry(dbSubFoo).CurrentValues.SetValues(newSubFoo);
else
// Insert subFoos into the database that are not
// in the dbFoo.subFoo collection
dbFoo.SubFoo.Add(newSubFoo);
}
// and the same for AnotherSubFoo...
db.SaveChanges();
return newFoo;
}
Just thought I would post the link below as it really helped me understand how to update related entities.
Updating related data with the entity framework in an asp net mvc application
NOTE: I did change the logic slightly that is shown in the UpdateInstructorCourses function to suit my needs.
You can also call the tables independently
MyContext db = new MyContext
// I like using asynchronous calls in my API methods
var OldFoo = await db.Foo.FindAsync(id);
var OldAssociateFoo = db.AssociatedFoo;
var NewFoo = OldFoo;
var NewAssociatedFoo = OldAssociatedFoo;
NewFoo.SomeValue = "The Value";
NewAssociatedFoo.OtherValue = 20;
db.Entry(OldFoo).CurrentValues.SetValues(NewFoo);
db.Entry(OldAssociatedFoo).CurrentValues.SetValues(NewAssociatedFoo);
await db.SaveChangesAsync();

Categories

Resources