I'm writing an ASP.NET Core MVC web app that is a tool for handling a parts database. What I want is for the user to select a Part and then that will do some action, like delete that part from it's DB. However, I want this to be a generic action used by all the parts.
I have a class hierarchy which is:
Part
PartA
PartB
What I need is some method that I can call that will get the DbSet that my part belongs to. This is an example of what I'm looking to do:
Models
public class Part
{
public Nullable<int> ID { get; set; }
public string Brand { get; set; }
}
public class PartA : Part
{
public int Length { get; set; }
public List<Image> Images { get; set; }
}
public class PartB : Part
{
public int Durability { get; set; }
}
public class Image
{
public Nullable<int> ID { get; set; }
public string ImagePath { get; set; }
}
PartsDbContext
public class PartsDbContext : DbContext
{
public DbSet<PartA> PartAs { get; set; }
public DbSet<PartB> PartBs { get; set; }
}
PartsController
public IActionResult DeletePart (string partType, int id)
{
var partSet = GetDbSet(partType);
var part partSet.FirstOrDefault(e => e.ID == id);
if (part != null)
{
partSet.Remove(part);
_context.SaveChanges();
}
}
//function to find and return DbSet of the selected type
private DbSet<Part> GetDbSet (string partType)
{
switch (partType)
{
case "PartA":
return _context.PartAs;
case "PartB":
return _context.PartBs;
}
return null;
}
Now obviously this doesn't work because the compiler will complain that:
You can't convert type DbSet<PartA> to type DbSet<Part>
Anyone know how I might go about doing this?
This is really hacky, but sort of works.
public IActionResult DeletePart (string partType, int id)
{
Type type = GetTypeOfPart(partType);
var part = _context.Find(type, id);
var entry = _context.Entry(part);
entry.State = EntityState.Deleted;
_context.SaveChanges();
}
However, you really should just use polymorphism and generic abstract Controllers.
EDIT You can also use Explicit Loading for this.
private void LoadRelatedImages(IPart part)
{
_context.Entry(part)
.Collection(p => p.Images)
.Load();
}
Related
I'm working on an API endpoint working with enums, and I need to return a different Response (a class with different properties) based on enum value. I'm sure Generics can help me here but I can't figure how nor where.
Multiple entry points calling a common method (I have 16 differents types) that call a logic function :
public class ApiController : BaseController
{
public async Task<IActionResult> GetType1(FilterEnum filter)
{
return await this.PrepareResponseForFilterAndType(filter, TypeEnum.Type1);
}
public async Task<IActionResult> GetType2(FilterEnum filter)
{
return await this.PrepareResponseForFilterAndType(filter, TypeEnum.Type2);
}
public async Task<IActionResult> GetType16(FilterEnum filter)
{
return await this.PrepareResponseForFilterAndType(filter, TypeEnum.Type16);
}
private async Task<IActionResult> PrepareResponseForFilterAndType(FilterEnum filter, TypeEnum type)
{
// Call to logic, with return type of IEnumerable<IResponse>
var response = await Logic.DoBusiness(this.UnitOfWork, filter, type);
//// Do stuff before return
return this.Json(response);
}
}
In logic, I apply filters (both FilterEnum and TypeEnum) to grab requested data, update data if necessary, whatever.
Then, I have to return a different Response depending on the TypeEnum: most responses share common values, but not all, and lots of responses have specific values.
My issue is here: the code work, it's not perfect, but it work. Still, it smell.
I want to change what the Interface and the Switch allow me to do for something better and cleaner.
As you will see with the interface and classes that implements it, all properties from the interface must be in each Type1Response, Type2Response, ..., Type16Response classes as well. That means if some of the interface properties are not present for this "TypeX", they will still be in the response with a null value that is not meaningful for this Type.
public class Logic
{
public async Task<IEnumerable<IResponse>> DoBusiness(UnitOfWork unitOfWork, FilterEnum filter, TypeEnum type)
{
//// Apply filter and return a collection of the relevant type from the db
var filteredElements = unitOfWork.ElementRepository.ListForFilterAndType(filter, type);
IEnumerable<IResponse> response = null;
switch (type)
{
case TypeEnum.Type1:
response = Mapper.Map<IEnumerable<Type1Response>>(filteredElements);
break;
case TypeEnum.Type2:
response = Mapper.Map<IEnumerable<Type2Response>>(filteredElements);
break;
case TypeEnum.Type16:
response = Mapper.Map<IEnumerable<Type16Response>>(filteredElements);
break;
}
}
}
And the Interface and TypeXResponses
public interface IResponse
{
int Id { get; set; } // Mostly in all types but not everywhere
string Name { get; set; }
string Type { get; set; }
string Images { get; set; } // Needed for some types to format multiple urls
}
public class Type1Response : IResponse
{
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public string Images { get; set; } // No images for this type but will be present in the response cause of interface
public int Free { get; set; }
public int Capacity { get; set; }
}
public class Type2Response : IResponse
{
public int Id { get; set; } // No id nor name for this type but I must have them here cause of the interface
public string Name { get; set; } // No id nor name for this type but I must have them here cause of the interface
public string Type { get; set; }
public string Description { get; set; }
public string Images { get; set; }
}
public class Type16Response : IResponse
{
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public string Description { get; set; }
public string Images { get; set; }
}
So I'm looking for a cleaner way to implement this, that work better (no unnecessary null values), without duplicating 16 times the same code, except the TypeXResponse.
Thanks in advance.
NET Core web API project, and there is one Get action method that returns an entity(Keyfield) when passing in an id. I would like to modify this method so that it returns the same entity but now together with its children entities(Referencefields). How can I get this within the same call to the database using LINQ method syntax inside this Get method?
My get method:
[HttpGet("{id}")]
public async Task<ActionResult<KeyField>> GetKeyField(int id)
{
var keyField = await _context.KeyFields.FindAsync(id);
if (keyField == null)
{
return NotFound();
}
return keyField;
}
My two classes:
public class KeyField
{
public int KeyFieldId { get; set; }
public string KeyName { get; set; }
public string ShortDesc { get; set; }
public List<ReferenceField> ReferenceFields { get; set; }
The child entity:
public class ReferenceField
{
public int ReferenceFieldId { get; set; }
public int KeyFieldId { get; set; }
public virtual KeyField KeyField { get; set; }
public string FieldName { get; set; }
public string Instructions { get; set; }
}
Use Eager loading
[HttpGet("{id}")]
public async Task<ActionResult<KeyField>> GetKeyField(int id)
{
var keyField = await _context.Set<KeyField>()
.Where(x => x.KeyFieldId == id)
.Include(x => x.ReferenceFields)
.FirstOrDefaultAsync();
if (keyField == null)
{
return NotFound();
}
return keyField;
}
Or enable Lazy Loading as another option.
I'm attempting to bind some query string parameters that is indexed by string keys but i can't seem to be getting it to work
here are the values i was trying to bind
search[value]: Exception happ...
search[regex]: false
here is the model i'm trying to bind it with
getLogsAjax(DataTableAjaxPostModel model)
public class DataTableAjaxPostModel
{
public int draw { get; set; }
public int start { get; set; }
public int length { get; set; }
public List<Column> columns { get; set; }
public search search { get; set; }
public List<Order> order { get; set; }
}
public class search
{
public string value { get; set; }
public string regex { get; set; }
}
the rest of the model is being bind correctly except for the search class object, i tripled check that the request contains values for that object, what am i missing here?
p.s. the same code was supposedly working pre .net core
A little more background of the code would be helpful such as the code section that is actually doing the binding however here is a dotnetcore controller example with query parameter binding. Also common practice in C# are class names and fields are both uppercase FYI.
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
[Route("api/[controller]")]
public class SampleController : Controller
{
[HttpGet]
[Route("")]
public IActionResult ExampleGet([FromQuery] DataTableAjaxPostModel dataTableAjaxPostModel)
{
// You should be able to debug and see the value here
var result = dataTableAjaxPostModel.search;
return Ok();
}
public class DataTableAjaxPostModel
{
public int draw { get; set; }
public int start { get; set; }
public int length { get; set; }
public List<Column> columns { get; set; }
public search search { get; set; }
public List<Order> order { get; set; }
}
public class search
{
public string value { get; set; }
public string regex { get; set; }
}
}
You don't need to bind each field manually. Using reflection will make it easily.
Aslo, there's no need bind those outer model's properties (DataTableAjaxPostModel's properties) manually. That's because they will be done by the built-in model binder.
Implementation
create a custom binder QueryStringDictSyntaxBinder<TModel>:
internal class QueryStringDictSyntaxBinder<TModel> : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
try
{
var result = Activator.CreateInstance<TModel>();
foreach(var pi in typeof(TModel).GetProperties())
{
var modelName = bindingContext.ModelName;
var qsFieldName = $"{modelName}[{pi.Name}]";
var field= bindingContext.HttpContext.Request.Query[qsFieldName].FirstOrDefault();
if(field != null){
pi.SetValue(result,field);
}
// do nothing if null , or add model binding failure messages if you like
}
bindingContext.Result = ModelBindingResult.Success(result);
}
catch
{
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
And then decorate the search property with a [ModelBinder(typeof(QueryStringDictSyntaxBinder<search>))] :
public class DataTableAjaxPostModel
{
public int draw { get; set; }
public int start { get; set; }
public int length { get; set; }
public List columns { get; set; }
[ModelBinder(typeof(QueryStringDictSyntaxBinder<search>))]
public search search { get; set; }
public List order { get; set; }
}
Test Case:
I test it with the following requests, and it works fine for me:
?draw=1&search[value]=abc&search[regex]=(.*)&
?draw=1&sEarCh[value]=opq&Search[regex]=([^123]*)&
?draw=1&seaRch[value]=rst&Search[regex]=(.*)&
?draw=1&Search[value]=abc&
?draw=1&
seems like no one has an answer for this, so i took a different route and wrote my own custom binder, if a better answer came, ill accept it instead of this one, probably will refactor it later (hahaha IKR!)
public class DTModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
try
{
var result = new DataTableAjaxPostModel();
if (bindingContext.HttpContext.Request.Query.Keys.Contains("draw"))
result.draw = int.Parse(bindingContext.ValueProvider.GetValue("draw").FirstValue);
if (bindingContext.HttpContext.Request.Query.Keys.Contains("search[value]") &&
bindingContext.HttpContext.Request.Query.Keys.Contains("search[regex]"))
result.search = new search()
{
regex = bindingContext.ValueProvider.GetValue("search[regex]").FirstValue,
value = bindingContext.ValueProvider.GetValue("search[value]").FirstValue
};
//...
bindingContext.Result = ModelBindingResult.Success(result);
}
catch
{
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
I have a property entitiy. Each property can have multiple images.
I have a method that retrieves the property data, as well as the images, but only one image is being returned when I test the API in post man.
This is the interface
IPropertyRepository :
Task<Property> GetProperty(int id);
PropertyRepository :
public async Task<Property> GetProperty(int id)
{
var property = await _context.Property.Include(ph => ph.Photos)
.FirstOrDefaultAsync(p => p.Id == id);
return property;
}
PropertyController :
[HttpGet("{id}", Name = "GetProperty")]
public async Task<IActionResult> GetProperty(int id)
{
var property = await _repo.GetProperty(id);
var propertyToReturn = _mapper.Map<PropertyForDetailedDto>(property);
return Ok(property);
}
This is the class of the DataTransferObject that is used above.
public class PropertyForDetailedDto
{
public int Id { get; set; }
public string Town { get; set; }
public string County { get; set; }
public string Address { get; set;
public int UserId {get; set; }
public ICollection<Photo> Photos { get; set; }
}
Any ideas here?
AutoMapper
public class AutoMapperProfiles : Profile
{
public AutoMapperProfiles()
{
CreateMap<UserForRegisterDto, User>();
CreateMap<PropertyForCreateDto, Property>();
CreateMap<PropertyToReturnDto, Property>();
CreateMap<PhotoForCreationDto, Photo>();
CreateMap<PropertyToReturnDto, Property>();
}
I have solved my issue.
In my PropertyForDetailedDto I had to include another DTO for the photos as opposed to a Model.
public ICollection<PhotosForDetailedDto> Photos { get; set;}
public ICollection<Photo> Photos { get; set; }
Next, I had to map both the PropertyForDetailedDto and the above PhotoForDetailedDto
CreateMap<PropertyForDetailedDto, Property>();
CreateMap<Photo, PhotosForDetailedDto>();
I know the correct property as well as all corresponding that belong to property returned.
I am trying to get data from a table with a get request with a controller. When I make the request with a normal table (TestTable) it is ok, but if I make the request with a relational table I get the fail message:
"The 'ObjectContent`1' type failed to serialize the response body for
content type 'application/xml; charset=utf-8'."
My controller (Mdata):
namespace ScThAsp.Controllers
{
public class MDataController : ApiController
{
public IEnumerable<Måledata> Get()
{
using (var e = new SCTHDBEntities())
{
return e.Måledata.ToList();
}
}
public TestTable Get(int id)
{
using (SCTHDBEntities entities = new SCTHDBEntities())
{
return entities.TestTable.FirstOrDefault(e => e.Id == 1);
}
}
}
}
My Table for måledata is:
public partial class Måledata
{
public int MDid { get; set; }
public Nullable<int> BBid { get; set; }
public Nullable<decimal> Måling1 { get; set; }
public Nullable<decimal> Måling2 { get; set; }
public Nullable<decimal> Måling3 { get; set; }
public Nullable<decimal> Måling4 { get; set; }
public Nullable<System.DateTime> RegTid { get; set; }
public virtual BuildBoard BuildBoard { get; set; }
}
My database looks like:
Database
See link..
I think I mayby should make a inner join with the other table connected to Måledata table - I am not sure how to do that in a EF environment.
I have really tried a lot now - hope for an answer. Thanks
Your class Måledata contains more data that you presented (it is marked as partial) and probably contains stuff related to EF. This magic stuff is not serializable. To avoid problem rewrite results to a plain object with properties you need. This object must be serializable (if contains plain properties and classes it will).
Building upon Piotr Stapp's answer you need to create a DTO (Data Transfer Object) for your Måledata which contains properties as your model, Måledata other than the EF properties. Use some sort of Mapper, maybe AutoMapper to map the required properties in your final response.
public class MaledataDTO
{
public int MDid { get; set; }
public int? BBid { get; set; }
public decimal? Måling1 { get; set; }
public decimal? Måling2 { get; set; }
public decimal? Måling3 { get; set; }
public decimal? Måling4 { get; set; }
public DateTime? RegTid { get; set; }
//... other properties
}
public IEnumerable<MaledataDTO> Get()
{
using (var e = new SCTHDBEntities())
{
var result = e.Måledata.ToList();
return Mapper.Map<List<MaledataDTO>>(result);
}
}
I found 2 solutions.
1) Solution was with Automapper (thanks Abdul). Installing automapper and a Using Automapper. Added a class called MåledataDTO : ` public class MåledataDTO
{
public int MDid { get; set; }
public int? BBid { get; set; }
public decimal? Måling1 { get; set; }
public decimal? Måling2 { get; set; }
public decimal? Måling3 { get; set; }
public decimal? Måling4 { get; set; }
public DateTime? RegTid { get; set; }
//... other properties
}
`
In my controller I used the following code
public IEnumerable<MåledataDTO> Get()
{
using (var e = new SCTHDBEntities())
{
Mapper.Initialize(config =>
{
config.CreateMap<Måledata, MåledataDTO>();
});
var result = e.Måledata.ToList();
return Mapper.Map<List<MåledataDTO>>(result);
2: In the second solution: In the picture you see the relations bewtween the tables - made in VS - but that creates a problem in the tables Get SET classes. The relation creates a Virtual object in the class - like mentioned before
public virtual BuildBoard BuildBoard { get; set; }
If you delete the relations and make the public partial class Måledata like
in this video
https://www.youtube.com/watch?v=4Ir4EIqxYXQ
the controller should then have one of two solutions:
using (SCTHDBEntities e = new SCTHDBEntities()) {
//this works
//var knud = new List<Måledata>();
//knud = (e.BuildBoard.Join(e.Måledata, c => c.BBid, o => o.BBid,
// (c, o) => o)).ToList();
//return knud;
//this works too
return (from p in e.BuildBoard
where p.BBid == 1
from r in e.Måledata
where r.BBid == p.BBid
select p).ToList();
That was that
Gorm