Entity Framework Core Query Specific Model both directions - asp.net

Let me preface this question with, I am VERY new to ASP.NET Core/EF Core.
My model look like this:
namespace MyProject.Models
{
public class DeviceContext : DbContext
{
public DeviceContext(DbContextOptions<DeviceContext> options) : base(options) { }
public DbSet<Device> Devices { get; set; }
public DbSet<DeviceLocation> DeviceLocations { get; set; }
}
public class Device
{
public int Id { get; set; }
public string DeviceName { get; set; }
public string ServerName { get; set; }
public string MacAddress { get; set; }
public string LastUpdate { get; set; }
public string WiredIPAddress { get; set; }
public string WirelessIPAddress { get; set; }
public DeviceLocation DeviceLocation { get; set; }
}
public class DeviceLocation
{
public int Id { get; set; }
public string Location { get; set; }
public virtual ICollection<Device> Devices { get; set; }
}
}
I would like to be able to fetch a specific Device based on DeviceName, but I would also like to fetch ALL the devices in a particular Location.
I think the following would work for the first question:
var _Devices = DeviceContext.Devices.FirstOrDefault(d => d.DeviceName == "BLA");
I am just having a hard time getting the second query to run. Ideally, the output would be rendered to JSON to be consumed by an API. I would like the output to look something like this:
{
"Locations": {
"NYC": ["ABC", "123"],
"Boston": ["DEF", "456"],
"Chicago": ["GHI", "789"]
}
}
UPDATE
If I use the following code, it give me the following error:
Code:
// Grouping by ProfileName
var devices = DeviceContext.DeviceLocations.Include(n => n.Device).ToList();
var result = new { success = true, message = "Successfully fetched Devices", data = devices };
return JsonConvert.SerializeObject(result);
Error:
Additional information: Self referencing loop detected for property 'DeviceLocation' with type 'Project.Models.DeviceLocation'. Path 'data[0].Device[0]'.

You can try as shown below.
Note : Use Eager Loading with Include.
using System.Data.Entity;
var devicesList = DeviceContext.DeviceLocations.Where(d=>d.Location = "Your-Location-Name")
.Include(p => p.Devices)
.ToList();
Update :
var devicesList = DeviceContext.DeviceLocations
.Include(p => p.Devices)
.ToList();

Related

Missing type map configuration exception is showing in my code

I have the following code in the controller and showing exception.
[HttpGet("{id}")]
public IActionResult GetCategoryGoalsById(int id)
{
try
{
var categories = _unitOfWork.Category.GetCategoryByGoalId(id);
if (categories == null)
{
_loggerManager.LogError($"Category with id: {id}, hasn't been found in db.");
return NotFound();
}
else
{
_loggerManager.LogInfo($"Returned category with id: {id}");
var categoryResult = _mapper.Map<CategoryDetailVm>(categories);
return Ok(categoryResult);
}
}
catch (Exception ex)
{
_loggerManager.LogError($"Something went wrong inside categoryResult action: {ex.Message}");
return StatusCode(500, "Internal server error");
}
}
Where is the entity class is like this:
public class Category
{
public int Id { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public string CategoryName { get; set; }
[ForeignKey(nameof(Goals))]
public int GoalId { get; set; }
public Goals Goals { get; set; }
}
and vm class for the module class written as:
public class CategoryDetailVm
{
public int Id { get; set; }
public string CategoryName { get; set; }
}
The code is written in repository pattern with UnitofWork and the repository part is written as:
public IEnumerable<Category> GetCategoryByGoalId(int goalId)
{
return FindByCondition(g => g.Goals.Id.Equals(goalId)).ToList();
}
following exception is showing here, how can I resolve the following problem:
ex {"Missing type map configuration or unsupported mapping.\r\n\r\nMapping types:\r\nObject ->
CategoryDetailVm\r\nSystem.Object ->
EthosAPI.ViewModelEntities.CategoryDetailVm"} System.Exception
{AutoMapper.AutoMapperMappingException}
It seems like you're missing an automapper mapping, did you add it?
https://docs.automapper.org/en/stable/Getting-started.html#how-do-i-use-automapper
eg. var config = new MapperConfiguration(cfg => cfg.CreateMap<CategoryDetailVm, Categorie>());
Also you're mapping an object to an entire list, so you should also have a mapping for lists, see:
https://docs.automapper.org/en/stable/Lists-and-arrays.html
So var categoryResult = _mapper.Map<CategoryDetailVm>(categories); should be more like var categoryResult = _mapper.Map<IEnumerable<CategoryDetailVm>>(categories); or something.

How to fix ‘Cannot create a DbSet for 'DM_NCC_ThueSuat' because this type is not included in the model for the context’ error in C# ASP.NET

I have a code block regards get a list like that in aspnetzero:
public async Task<List<DMNCCThueSuatDto>> GetDSThueSuat()
{
using (_unitOfWorkManager.Current.SetTenantId(null))
{
var lstthueSuat = await _dmThueSuatRepository.Query(t => t.Where(i =>
i.IsDeleted == false)).OrderBy("thuesuat_ma asc").ToListAsync();
return ObjectMapper.Map<List<DMNCCThueSuatDto>>(lstthueSuat);
}
}
I expected a list of DMNCCThueSuatDto is returned but the error
Cannot create a DbSet for 'DM_NCC_ThueSuat' because this type is not
included in the model for the context.
is always displayed.
Also, i had myown a declaration
public virtual DbSet DS_ThueSuat { get; set; }
in my DBContext.
In my mariadb database, i had a table called "vs_dm_ncc_thuesuat"
and i have already declare a class for mapping to the table above
namespace VS.vHoaDon.Domains.DanhMuc.DMNhaCungCap
{
[Table("vs_dm_ncc_thuesuat")]
[MultiTenancySide(MultiTenancySides.Host)]
public class DM_NCC_ThueSuat : FullAuditedEntity
{
public int ThueSuat_Ma { get; set; }
public string ThueSuat_Ten { get; set; }
public int ThueSuat_GiaTri { get; set; }
public bool ThueSuat_HieuLuc { get; set; }
public DateTime? ThueSuat_BatDau { get; set; }
public DateTime? ThueSuat_KetThuc { get; set; }
}
}
I don't know why?
Any helps is appreciated.
Thank you so much.

Using DTO's with OData & Web API

Using Web API and OData, I have a service which exposes Data Transfer Objects instead of the Entity Framework entities.
I use AutoMapper to transform the EF Entities into their DTO counter parts using ProjectTo():
public class SalesOrdersController : ODataController
{
private DbContext _DbContext;
public SalesOrdersController(DbContext context)
{
_DbContext = context;
}
[EnableQuery]
public IQueryable<SalesOrderDto> Get(ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config);
}
[EnableQuery]
public IQueryable<SalesOrderDto> Get([FromODataUri] string key, ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key)
.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config);
}
}
AutoMapper (V4.2.1) is configured as follows, note the ExplicitExpansion() which prevents serialisation auto expanding navigation properties when they are not requested:
cfg.CreateMap<SalesOrderHeader, SalesOrderDto>()
.ForMember(dest => dest.SalesOrderLines, opt => opt.ExplicitExpansion());
cfg.CreateMap<SalesOrderLine, SalesOrderLineDto>()
.ForMember(dest => dest.MasterStockRecord, opt => opt.ExplicitExpansion())
.ForMember(dest => dest.SalesOrderHeader, opt => opt.ExplicitExpansion());
ExplicitExpansion() then creates a new problem where the following request throws an error:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines
The query specified in the URI is not valid. The specified type member 'SalesOrderLines' is not supported in LINQ to Entities
The navigation property SalesOrderLines is unknown to EF so this error is pretty much what I expected to happen. The question is, how do I handle this type of request?
The ProjectTo() method does have an overload that allows me to pass in an array of properties that require expansion, I found & modified the extension method ToNavigationPropertyArray to try and parse the request into a string array:
[EnableQuery]
public IQueryable<SalesOrderDto> Get([FromODataUri] string key, ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key)
.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config, null, queryOptions.ToNavigationPropertyArray());
}
public static string[] ToNavigationPropertyArray(this ODataQueryOptions source)
{
if (source == null) { return new string[]{}; }
var expandProperties = string.IsNullOrWhiteSpace(source.SelectExpand?.RawExpand) ? new List<string>().ToArray() : source.SelectExpand.RawExpand.Split(',');
for (var expandIndex = 0; expandIndex < expandProperties.Length; expandIndex++)
{
// Need to transform the odata syntax for expanding properties to something EF will understand:
// OData may pass something in this form: "SalesOrderLines($expand=MasterStockRecord)";
// But EF wants it like this: "SalesOrderLines.MasterStockRecord";
expandProperties[expandIndex] = expandProperties[expandIndex].Replace(" ", "");
expandProperties[expandIndex] = expandProperties[expandIndex].Replace("($expand=", ".");
expandProperties[expandIndex] = expandProperties[expandIndex].Replace(")", "");
}
var selectProperties = source.SelectExpand == null || string.IsNullOrWhiteSpace(source.SelectExpand.RawSelect) ? new List<string>().ToArray() : source.SelectExpand.RawSelect.Split(',');
//Now do the same for Select (incomplete)
var propertiesToExpand = expandProperties.Union(selectProperties).ToArray();
return propertiesToExpand;
}
This works for expand, so now I can handle a request like the following:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines
or a more complicated request like:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($expand=MasterStockRecord)
However, more complicated request that try to combine $select with $expand will fail:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($select=OrderQuantity)
Sequence contains no elements
So, the question is: am I approaching this the right way?
It feels very smelly that I would have to write something to parse and transform the ODataQueryOptions into something EF can understand.
It seems this is a rather popular topic:
odata-expand-dtos-and-entity-framework
how-to-specify-the-shape-of-results-with-webapi2-odata-with-expand
web-api-queryable-how-to-apply-automapper
how-do-i-map-an-odata-query-against-a-dto-to-another-entity
While most of these suggest using ProjectTo, none seem to address serialisation auto expanding properties, or how to handle expansion if ExplictExpansion has been configured.
Classes and Config below:
Entity Framework (V6.1.3) entities:
public class SalesOrderHeader
{
public string SalesOrderNumber { get; set; }
public string Alpha { get; set; }
public string Customer { get; set; }
public string Status { get; set; }
public virtual ICollection<SalesOrderLine> SalesOrderLines { get; set; }
}
public class SalesOrderLine
{
public string SalesOrderNumber { get; set; }
public string OrderLineNumber { get; set; }
public string Product { get; set; }
public string Description { get; set; }
public decimal OrderQuantity { get; set; }
public virtual SalesOrderHeader SalesOrderHeader { get; set; }
public virtual MasterStockRecord MasterStockRecord { get; set; }
}
public class MasterStockRecord
{
public string ProductCode { get; set; }
public string Description { get; set; }
public decimal Quantity { get; set; }
}
OData (V6.13.0) Data Transfer Objects:
public class SalesOrderDto
{
[Key]
public string SalesOrderNumber { get; set; }
public string Customer { get; set; }
public string Status { get; set; }
public virtual ICollection<SalesOrderLineDto> SalesOrderLines { get; set; }
}
public class SalesOrderLineDto
{
[Key]
[ForeignKey("SalesOrderHeader")]
public string SalesOrderNumber { get; set; }
[Key]
public string OrderLineNumber { get; set; }
public string LineType { get; set; }
public string Product { get; set; }
public string Description { get; set; }
public decimal OrderQuantity { get; set; }
public virtual SalesOrderDto SalesOrderHeader { get; set; }
public virtual StockDto MasterStockRecord { get; set; }
}
public class StockDto
{
[Key]
public string StockCode { get; set; }
public string Description { get; set; }
public decimal Quantity { get; set; }
}
OData Config:
var builder = new ODataConventionModelBuilder();
builder.EntitySet<StockDto>("Stock");
builder.EntitySet<SalesOrderDto>("SalesOrders");
builder.EntitySet<SalesOrderLineDto>("SalesOrderLines");
I have created an Automapper explicit navigation expansion utility function that should work with N-deph expands. Posting it here since it might help someone.
public List<string> ProcessExpands(IEnumerable<SelectItem> items, string parentNavPath="")
{
var expandedPropsList = new List<String>();
if (items == null) return expandedPropsList;
foreach (var selectItem in items)
{
if (selectItem is ExpandedNavigationSelectItem)
{
var expandItem = selectItem as ExpandedNavigationSelectItem;
var navProperty = expandItem.PathToNavigationProperty?.FirstSegment?.Identifier;
expandedPropsList.Add($"{parentNavPath}{navProperty}");
//go recursively to subproperties
var subExpandList = ProcessExpands(expandItem?.SelectAndExpand?.SelectedItems, $"{parentNavPath}{navProperty}.");
expandedPropsList = expandedPropsList.Concat(subExpandList).ToList();
}
}
return expandedPropsList;
}
You can call it with :
var navExp = ProcessExpands(options?.SelectExpand?.SelectExpandClause?.SelectedItems)
it will return a list with ["Parent" ,"Parent.Child"]
I never really managed to work this one out. The ToNavigationPropertyArray() extension method helps a little, but does not handle infinite depth navigation.
The real solution is to create Actions or Functions to allow clients to request data requiring a more complicated query.
The other alternative is to make multiple smaller/simple calls then aggregate the data on the client, but this isn't really ideal.
When you want to mark something for explicit expansion in AutoMapper, you need to also opt-back-in when calling ProjectTo<>().
// map
cfg.CreateMap<SalesOrderHeader, SalesOrderDto>()
.ForMember(dest => dest.SalesOrderLines, opt => opt.ExplicitExpansion());
// updated controller
[EnableQuery]
public IQueryable<SalesOrderDto> Get()
{
return _dbContext.SalesOrders
.ProjectTo<SalesOrderDto>(
AutoMapperConfig.Config,
so => so.SalesOrderLines,
// ... additional opt-ins
);
}
While the AutoMapper wiki does state this, the example is perhaps a little misleading by not including the paired ExplicitExpansion() call.
To control which members are expanded during projection, set ExplicitExpansion in the configuration and then pass in the members you want to explicitly expand:

Linq Queries and Cannot implicitly convert type error

According to given project id(this id is coming to action as a parameter), I want to find this project and this project's issues and then I want to find some issues which has the "bug" type using linq queries in my MVC asp.net web application. But when I try below code in my action in ProjectController, I take this error: Cannot implicitly convert type 'System.Collection.Generic.List<System.Collections.Generic.List<MVCTest1.Models.Issue>>'to 'System.Collections.Generic.List<MVCTest1.Models.Issue>' and
List<Issue> issueList = (from i in db.Projects where i.projectID == projectId select i.Issues).ToList();
List<Issue> bugList = (from bug in issueList where bug. ) --> I cannot reach properties of bug issue
Here my project Model:
public class Project
{
public int projectID { get; set; }
public string projectName { get; set; }
public string descriptionProject { get; set; }
public Project parentProject { get; set; }
public string identifier { get; set; }
public DateTime date { get; set; }
public List<Project> subProjects { get; set; }
public virtual List<Issue> Issues { get; set; }
}
and my Issue Model:
public class Issue
{
public int issueID { get; set; }
public string description { get; set; }
public string subject { get; set; }
public IssueStatus? status { get; set; }
public Issue parentTask { get; set; }
public DateTime startDate { get; set; }
public DateTime dueDate { get; set; }
public int done { get; set; }
public IssuePriority? priority { get; set; }
public IssueType? type { get; set; }
public virtual List<User> Users { get; set; }
}
finally my enum:
public enum IssueType
{
Bug = 0,
Feature = 1,
Support = 2,
Operation = 3
}
Thanks in advance.
// edit 2
var project = db.Projects.Single(p => p.projectID == projectId);
var issues = project.Issues;
var bugIssues = from bug in issues where bug.type == 0 select bug;
return PartialView(bugIssues);
When I write this I got this error :
The model item passed into the dictionary is of type 'System.Linq.Enumerable+WhereListIterator1[MVCTest1.Models.Issue]', but this dictionary requires a model item of type 'System.Collections.Generic.List1[MVCTest1.Models.Issue]'.
The problem is that your Issues property is already a List<Issue>. I suspect you want something like:
// TODO: Fix property naming...
var project = db.Projects.Single(p => p.projectId == projectId);
var issues = project.Issues;
Now issues will be a List<Issue> rather than a List<List<Issue>>.
EDIT: For the next problem, you've got an IEnumerable<Issue> but you're expecting a List<Issue>, so you need to call ToList() at that point. For example:
var project = db.Projects.Single(p => p.projectId == projectId);
return PartialView(project.Issues.Where(b => b.type == 0).ToList());
The problem is in the expected model of the MVC view, it expects a System.Collections.Generic.List<T>, but you gave a System.Linq.Enumerable.
Try do this.
return PartialView(bugIssues.ToList());

RavenDB Query on Datetime with value in collection offset

I am trying to query RavenDB on a Datetime which is being offset by a entry in a collection. As shown below, I have an AppointmentReminder object which contains many AppointmentReminderJobs. I'd like to query for AppointmentReminders where the AppointmentReminderJob is due to run.
My models are as follows:
public class AppointmentReminder
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public DateTime AppointmentDateTime { get; set; }
public ReminderStatus ReminderStatus { get; set; }
public List<AppointmentReminderJob> AppointmentReminderJobs { get; set; }
}
public class AppointmentReminderJob
{
public JobStatus JobStatus { get; set; }
public int DaysPrior { get; set; }
}
My controller and attempt to retrieve a list of AppointmentReminders which have current jobs to run (I know this Where clause isn't complete, but I've tried to simplify it with no luck):
public ActionResult GetJobsQueuedListCurrent()
{
var jobsqueuedlist = RavenSession.Query<AppointmentReminder>()
.Where(appointmentreminder => appointmentreminder.AppointmentReminderJobs.Any(x => appointmentreminder.AppointmentDateTime < DateTime.Now.AddDays(x.DaysPrior)))
.OrderBy(appointmentreminder => appointmentreminder.AppointmentDateTime)
.Take(20)
.ToList();
return View("List", jobsqueuedlist);
}
Calling the above yields a response of:
variable 'x' of type 'ProjectName.Models.AppointmentReminderJob' referenced from scope '', but it is not defined
I am trying to set up an index like so:
public class JobsQueuedListCurrent : AbstractIndexCreationTask<AppointmentReminder, JobsQueuedListCurrent.IndexResult>
{
public class IndexResult
{
public int Id { get; set; }
public DateTime JobDateTime { get; set; }
}
public JobsQueuedListCurrent()
{
Map = appointmentreminders => from appointmentreminder in appointmentreminders
from job in appointmentreminder.AppointmentReminderJobs
select new
{
Id = appointmentreminder.Id,
JobDateTime = appointmentreminder.AppointmentDateTime.AddDays(job.DaysPrior)
};
Store(x => x.Id, FieldStorage.Yes);
Store(x => x.JobDateTime, FieldStorage.Yes);
}
}
Now, I'm querying and getting expected results using:
var jobsqueuedlist = RavenSession.Query<JobsQueuedListCurrent.IndexResult, JobsQueuedListCurrent>()
.Where(x=>x.JobDateTime >= DateTime.Now)
.As<AppointmentReminder>()
.Take(20)
.ToList();
return View("List", jobsqueuedlist);
My last question regarding this would be, my map/index can definitely result in multiple entries of the same document id (appointmentreminder), but my resulting list contains only 1 instance of the document. I'm happy with the way that works, I'm just not sure if I should be performing a reduce or doing something else in my code or just let Raven handle it like it seems like it is doing?
You cannot create such a query. This would require RavenDB to perform computation during query, and that is not allowed.
RavenDB only allows queries on the data in the index.
What you can do it setup the computation in the index, and then query on that.

Resources