How to hide items, in an API reply, from a db query? - c#

I'm currently using MVC with EF to have a small server with API querying a SQL database. But in the API reply I'm not able to hide some parameters.
The main object
public class AssetItem
{
[Key]
public Int32 AssetId { get; set; }
public String AssetName { get; set; }
public int OdForeignKey { get; set; }
[ForeignKey("OdForeignKey")]
public OperationalDataItem OperationalDataItem { get; set; }
}
The other one:
public class OperationalDataItem
{
[Key]
public Int32 OperationalDataId { get; set; }
public String Comunity { get; set; }
public List<AssetItem> AssetItems { get; set; }
}
From what I have read, this should be ok, I have also set the context:
public AssetContext(DbContextOptions<AssetContext> options) : base(options)
{}
public DbSet<AssetItem> AssetItems { get; set; }
public DbSet<OperationalDataItem> OperationalDataItems { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AssetItem>().HasOne(p =>
p.OperationalDataItem).WithMany(b => b.AssetItems).HasForeignKey(p =>
p.OdForeignKey);
}
And the seeding in program.cs
context.AssetItems.Add(
new AssetItem { AssetName = "Test test", OdForeignKey = 1,
OperationalDataItem =
new OperationalDataItem {Comunity = "Comunity1" }});
So calling the API this results in:
{ "assetId":3,
"assetName":"Test test",
"odForeignKey":1,
"operationalDataItem":null }
From what I read this is because of the lazy loading, how can I hide the result operationalDataItem?
In case is not possible i have of course try to query for it and give it back and it give something like:
{ "assetId":3,
"assetName":"Test test",
"odForeignKey":1,
"operationalDataItem":
{ "operationalDataId":1,
"comunity":"Comunity1",
"assetItems":[
But in this case I would like to hide "assetsItems" in the reply to the FE.
How can I hide those parameters?
The API is quite simple, just an example code:
var todoItem = await _context.AssetItems.FindAsync((Int32)id);
var item = _context.OperationalDataItems.Find((Int32)todoItem.OdForeignKey);
todoItem.OperationalDataItem = item;
return todoItem

If you want to fetch data from the database, but you only want to fetch some properties, use Select. Usually this is more efficient than using Find, because you'll only transfer the data that you actually plan to use.
To fetch some properties of the assetItem that has primary key assetItemId:
var result = dbContext.AssetItems
.Where(assetItem => assetItem.AssetItmId = assetItemId)
.Select(assetItem => new
{
// Select only the properties that you plan to use
Id = assetItem.AssertItemId,
Name = assetItem.Name,
OperationalData = new
{
// again, select only the properties that you plan to use
Id = assetItem.OperationalData.OperationalDataId,
Community = assetItem.OperationalData.Community,
},
})
.FirstOrDefault();
Or the other way round:
Fetch several properties of all (or some) OperationalDataItems, each with some properties of all (or some) of its AssetItems:
var result = dbContext.OperqationalDataItems
.Where(operationalDataItem => ...) // only if you don't want all
.Select(operationalDataItem => new
{
Id = operationalDataItem.Id,
Community = operationalDataItem.Community
AssetItems = operationalDataItem.AssetItems
.Where(assetItem => ...) // only if you don't want all its assetItems
.Select(assetItem => new
{
// Select only the properties you plan to use:
Id = assetItem.Id,
...
// not useful: you know the value of the foreign key:
// OperationalDataId = assetItem.OperationalDataId,
})
.ToList();
})
.ToList(); // or: FirstOrDefault if you expect only one element
Entity framework knows your one-to-many relation and is smart enough to know which (group-)join is needed for your query.
Some side remarks
You've declare your many-relation a List<AssetItem>. Are you sure that operationalDataItem.AssetItems[4] has a defined meaning? Wouldn't it be better to stick to the entity framework code first conventions? This would also eliminate the need for most attributes and / or fluent API
public class OperationalDataItem
{
public int Id { get; set; }
public String Comunity { get; set; }
...
// Every OperationalDataItem has zero or more AssetItems (one-to-many)
public virtual ICollection<AssetItem> AssetItems { get; set; }
}
public class AssetItem
{
public int Id { get; set; }
public String Name { get; set; }
...
// every AssetItem belongs to exactly one OperationalDataItem, using foreign key
public int OperationDataItemId { get; set; }
public virtual OperationalDataItem OperationalDataItem { get; set; }
}
In entity framework the columns of a table are represented by the non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many)
Because I stuck to the conventions, no attributes nor fluent API is needed. Entity framework is able to detect the one-to-many relation and the primary and foreign keys. Only if I am not satisfied with the names or the types of the columns I would need fluent API.

Related

How to join two table in C# web API

I am new at C# entity framework. I am trying to build an API, but stuck in retrieving data from relational table.
I have a pei_crops table in MS SQL database, where c_id is the primary key. I have another table called pei_pests, where p_id is the primary key. Another table is pei_cropspests where I have built relation for which pest attack which crop. Multiple pests can attack one crop and one pest can attack multiple crops. In this pei_cropspests table I have put p_id as primary and foreign key and c_id as primary and foreign key as well.
pei_crops table:
c_id
c_name
c_description
1
Corn
NULL
pei_pests table:
p_id
p_name
p_URL
1
pest1
NULL
2
pest2
NULL
pei_cropspests table:
p_id
c_id
1
1
2
1
Now In my API I want to show something like that
[
{
"cId":1,
"pests":[
{
"pId":1,
"pName": pest1,
"pURL": null
},
{
"pId":2,
"pName": pest2,
"pURL": null
}
]
}
]
My get request looks like this so far in C# web API project:
[Route("Getspecific/{cropId}")]
[HttpGet]
public async Task<IActionResult> GetSpecific(int cropId)
{
var cropDetails = await _db.PeiCrops.Where(c=>c.CId == cropId).Include(i=>i.PeiCropspests).ToListAsync();
return Ok(cropDetails);
}
This code returns me only the pID and URL of the pest that effects cID number 1. But I also want the pest name and URL along with their id.
Could someone please show me how to do it. Maybe there is some way to join two table and show the data? I just do not know how to do it in C#. Any help appreciated. Thank you.
Entities class:
PeiCrop:
using System;
using System.Collections.Generic;
#nullable disable
namespace PEI_API.EF
{
public partial class PeiCrop
{
public PeiCrop()
{
PeiCropimages = new HashSet<PeiCropimage>();
PeiCropsdiseases = new HashSet<PeiCropsdisease>();
PeiCropspests = new HashSet<PeiCropspest>();
}
public int CId { get; set; }
public string CName { get; set; }
public string CPhotoUrl { get; set; }
public string CDescription { get; set; }
public virtual ICollection<PeiCropimage> PeiCropimages { get; set; }
public virtual ICollection<PeiCropsdisease> PeiCropsdiseases { get; set; }
public virtual ICollection<PeiCropspest> PeiCropspests { get; set; }
}
}
PeiPest:
using System;
using System.Collections.Generic;
#nullable disable
namespace PEI_API.EF
{
public partial class PeiPest
{
public PeiPest()
{
PeiCropspests = new HashSet<PeiCropspest>();
PeiPestimages = new HashSet<PeiPestimage>();
}
public int PId { get; set; }
public string PName { get; set; }
public string PPhotoUrl { get; set; }
public string PDescription { get; set; }
public virtual ICollection<PeiCropspest> PeiCropspests { get; set; }
public virtual ICollection<PeiPestimage> PeiPestimages { get; set; }
}
}
PeiCropspest:
using System.Collections.Generic;
#nullable disable
namespace PEI_API.EF
{
public partial class PeiCropspest
{
public int PId { get; set; }
public int CId { get; set; }
public virtual PeiCrop CIdNavigation { get; set; }
public virtual PeiPest PIdNavigation { get; set; }
}
}
You're pretty close, but you're also not entirely using EF like you could, I mean you do not actually have to make the relationship table yourself but could refer directly to a list of the entity pei_pests from the entity pei_crop and let EF create the other.
//Example just getting one property from each,
//but you can new a composite return type up if you wish, using select
var cropDetails = await _db.PeiCrops
.Where(c=>c.CId == cropId)
.Include(i=>i.PeiCropspests)
.ThenInclucde(t => t.Pests)
.Select(s => new { CropId = s.p_id, PestName = s.PeiCropsPests.Pest.p_name })
.ToListAsync();
https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.select?view=net-5.0
First, you need to configure the relationships :
class MyContext : DbContext
{
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PeiCropspest>()
.HasKey(cp => new { cp.PId, cp.CId });
//Configure one PeiPest to many PeiCropspest
modelBuilder.Entity<PeiCropspest>()
// Specify PeiCropspest's navigation property to one PeiPest
.HasOne(cp => cp.PIdNavigation)
// Specify PeiPest's navigaton property to many PeiCropspest
.WithMany(p => p.PeiCropspests)
// Specify PeiCropspest's navigation property
// to use this PeiCropspest's property as foreign key
.HasForeignKey(cp => cp.PId);
//Configure one PeiCrop to many PeiCropspest
modelBuilder.Entity<PeiCropspest>()
// Specify PeiCropspest's navigation shadow property to one PeiCrop
.HasOne<PeiCrop>()
// Specify PeiCrop's navigaton property to many PeiCropspest
.WithMany(c => c.PeiCropspests)
// Specify PeiCropspest's navigation shadow property
// to use this PeiCropspest's property as foreign key
.HasForeignKey(cp => cp.CId);
}
public DbSet<PeiCrop> PeiCrops { get; set; }
}
Then you can do a projection in the LINQ query :
public async Task<IActionResult> GetSpecific(int cropId)
{
var cropDetails = await _db.PeiCrops
.Where(c=>c.CId == cropId)
.Select(c => new {
cId = c.CId,
pests = c.PeiCropspests.Select(p => new {
pId = p.PIdNavigation.PId,
pName = p.PIdNavigation.PName,
pUrl = p.PIdNavigation.PPhotoUrl
})
})
.ToListAsync();
return Ok(cropDetails);
}
Do you know? From EF Core 5, it's possible to do many to many relationship without intermediary entity. This can simplify your entity model. cf. the documentation

Using .Select to get single item from sub list, then assigning individual properties of that item to view model

I have pretty much solved this problem but I am wondering whether there is a more efficient way of doing this using Entity framework / SQL.
Essentially, what i am doing is performing a subquery to get a SINGLE item on a list of objects that are connected to a parent entity. I then want to extract only a few columns from that single entity.
The first way, which doesn't work but shows my possible thought process was to put each object into a temporary variable and then create the view:
_context.IcoInfos.Select((i) =>
{
var reward = i.SocialRewards.OrderByDescending(s => s.EndDate).FirstOrDefault();
return new IcoInfoRewardCountViewModel()
{
CampaignName = i.Name,
CurParticipants = reward.CurParticipants,
Title = reward.CustomTitle,
IsLive = reward.IsLive
};
});
The second way, which works, I am creating a temporary model which stores the single database row of the sublist result...
_context.IcoInfos.Select((i) => new
{
Reward = i.SocialRewards.OrderByDescending(s => s.EndDate).FirstOrDefault(),
IcoName = i.Name
}).Select(t => new IcoInfoRewardCountViewModel()
{
CampaignName = t.IcoName,
CurParticipants = t.Reward.CurParticipants,
Title = t.Reward.CustomTitle,
IsLive = t.Reward.IsLive
}).ToList();
My question is, is this second way the only/best way to achieve this?
Your second approach is ok but for bigger application will cause you trouble if application growth larger and you have a lot information to store in the model.
So I think you can use automapper to make your code more clean.
Example
To use autoampper I need to define a model class and DTO class that share some same properties.
public class Comment
{
public string Content { get; set; }
public virtual Comment ParentComment { get; set; }
public virtual Post Post { get; set; }
public virtual User? User { get; set; }
public CommentStatus CommentStatus { get; set; }
}
public class CommentDto
{
public int Id { get; set; }
public Guid UniqeId { get; set; }
public string Content { get; set; }
public Comment ParentComment { get; set; }
public CommentStatus CommentStatus { get; set; }
public DateTime DateCreated { get; set; }
}
I also need to define the profile class to register mapping
public class CommentProfile : Profile
{
public CommentProfile()
{
CreateMap<Comment, CommentDto>(MemberList.None).ReverseMap();
}
}
Then I will need to register into DI container in startup.cs
services.AddAutoMapper();
Then I can use like this
var comments = await _unitOfWork.Repository<Comment>().Query()
.Include(x => x.User)
.Include(c => c.Post)
.Select(x => new CommentViewModel
{
Comment = _mapper.Map<Comment, CommentDto>(x),
})
.ToListAsync();
It will make the code more clear and I dont have to do manual mapping

How can I get the count of a list in an Entity Framework model without including/loading the entire collection?

I have a model in Entity Framework Core that goes something like this:
public class Anime
{
public int EpisodeCount { get { return Episodes.Count() } }
public virtual ICollection<Episode> Episodes { get; set; }
}
I'm having the issue of EpisodeCount being 0. The solution currently is to run a .Include(x => x.Episodes) within my EF query, but that loads the entire collection of episodes where it's not needed. This also increases my HTTP request time, from 100ms to 700ms which is just not good.
I'm not willing to sacrifice time for simple details, so is there a solution where I can have EF only query the COUNT of the episodes, without loading the entire collection in?
I was suggested to do this
var animeList = context.Anime.ToPagedList(1, 20);
animeList.ForEach(x => x.EpisodeCount = x.Episodes.Count());
return Json(animeList);
but this also returns 0 in EpisodeCount, so it's not a feasible solution.
You need to project the desired data into a special class (a.k.a. ViewModel, DTO etc.). Unfortunately (or not?), in order to avoid N + 1 queries the projection must not only include the count, but all other fields as well.
For instance:
Model:
public class Anime
{
public int Id { get; set; }
public string Name { get; set; }
// other properties...
public virtual ICollection<Episode> Episodes { get; set; }
}
ViewModel / DTO:
public class AnimeInfo
{
public int Id { get; set; }
public string Name { get; set; }
// other properties...
public int EpisodeCount { get; set; }
}
Then the following code:
var animeList = db.Anime.Select(a => new AnimeInfo
{
Id = a.Id,
Name = a.Name,
EpisodeCount = a.Episodes.Count()
})
.ToList();
produces the following single SQL query:
SELECT [a].[Id], [a].[Name], (
SELECT COUNT(*)
FROM [Episode] AS [e]
WHERE [a].[Id] = [e].[AnimeId]
) AS [EpisodeCount]
FROM [Anime] AS [a]

Entity framework relationships

I have these three entities:
public class Dog
{
public int DogId { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public bool Checked { get; set; }
public string DogImage { get; set; }
public virtual ICollection<Result> Results { get; set; }
}
public class Event
{
public int EventId { get; set; }
public string EventName { get; set; }
public string EventLocation { get; set; }
public string EventType { get; set; }
public string EventDate { get; set; }
public virtual ICollection<Result> Results { get; set; }
}
public class Result
{
public int ResultId { get; set; }
public int Track { get; set; }
public int Obedience { get; set; }
public int Protection { get; set; }
[ForeignKey("Dog")]
public int DogId { get; set; }
public virtual Dog Dog { get; set; }
[ForeignKey("Event")]
public int EventId { get; set; }
public virtual Event Event { get; set; }
}
I´ve been getting help from here before in order to set it up like this.
Entity Framework errors when trying to create many-to-many relationship
So the way it is now I guess the result is the "glue" that ties these classes together containing foreign keys to the two other tables.
What I have been trying to achieve for days now is to:
Create an event.
Add dogs to the event.
Add results to the dogs participating in the choosenEvent.
Lets say I create an event like this:
[HttpPost]
public ActionResult CreateEvent(Event newEvent)
{
newEvent.EventDate = newEvent.EventDate.ToString();
_ef.AddEvent(newEvent);
return View();
}
Now I guess the next step would be to add a list of dogs to this event and in order to do that I need to somehow use my result-class since that's the "glue"-class. Please let me know if I'm even on the right track here.
It is not really a good idea to do many to many relationships like how you've done. See here
In order to get a proper many to many relationship, mapped in the proper way in the database, that doesn't have pitfalls, I would try it this way:
public class Dog {}
public class Event {}
public class Result {}
// This is a linking table between Dog and Results
public class DogResult
{
public int Id {get;set;}
public int DogId {get;set;}
public int ResultId {get;set;}
}
// This is a linking table between Events and Results
public class EventResult
{
public int Id {get;set;}
public int EventId {get;set;}
public int ResultId {get;set;}
}
When you now write your query you can do this:
using (var context = new DbContext())
{
var dogs = context.Dogs();
var dogResults = context.DogResults();
var results = context.Results();
var dogsAndResults = dogs.Join(
dogResults,
d => d.Id,
r => r.DogId,
(dog, dogResult) => new { dog, dogResult })
.Join(
results,
a => a.dogResult.ResultId,
r => r.Id,
(anon, result) => new { anon.dog, result });
}
It is a bit nasty looking, but it will give you back a list of anonymous objects containing a Dog and its related Result. But obviously it would be better to do this in a stored proc:
using (var context = new DbContext())
{
var results = context.Database.ExecuteStoreQuery<SomeResultDto>("SELECT * .... JOIN ... ");
}
This is cleaner, because you are using SQL.
This is a more complex way of dealing with it. But far more performant, especially if you understand fully how entity framework executes LINQ.
Obviously if you want to create these links:
using (var context = new DbContext())
{
context.Dogs.AddRange(dogs); // dogs being a list of dog entities
context.Results.AddRange(results); // events being a list of results entities
context.DogResults.AddRange(dogResults); // a list of the links
}
It is completely up to you how you create these links. To turn this into a sproc as well, you want to create some custom User Defined Table Types and use them as a Table Value Parameter.
var dogResults = dogs.SelectMany( d => results.Select ( r => new DogResult { DogId = d.Id, ResultId = r.Id } ) );
That is a beast of a LINQ query and basically it gets every dog and links it to every result. Run it in LinqPad and Dump the values.
I've only done this using the fluent method (when I was learning I found you can do everything in fluent, but not with annotations, so I've not looked into them), the following creates a many to many between my Unit entity and my UnitService entity:
modelBuilder.Entity<Unit>()
.HasMany<UnitService>(u => u.Services)
.WithMany(us => us.Units);
This code is in the protected override void OnModelCreating(DbModelBuilder modelBuilder) method.
In your case Event is Unit and Dog is UnitService.
Oh ooops, you don't need that at all, your 'join' table is your results table, in my case I don't care about the join table so its all hidden.
Maybe something like:
modelBuilder.Entity<Result>()
.HasMany<Event>(e => e.Results);
modelBuilder.Entity<Result>()
.HasMany<Dog>(d => d.Results);

Calculated column in EF Code First

I need to have one column in my database calculated by database as (sum of rows) - (sum of rowsb). I'm using code-first model to create my database.
Here is what I mean:
public class Income {
[Key]
public int UserID { get; set; }
public double inSum { get; set; }
}
public class Outcome {
[Key]
public int UserID { get; set; }
public double outSum { get; set; }
}
public class FirstTable {
[Key]
public int UserID { get; set; }
public double Sum { get; set; }
// This needs to be calculated by DB as
// ( Select sum(inSum) FROM Income WHERE UserID = this.UserID)
// - (Select sum(outSum) FROM Outcome WHERE UserID = this.UserID)
}
How can I achieve this in EF CodeFirst?
You can create computed columns in your database tables. In the EF model you just annotate the corresponding properties with the DatabaseGenerated attribute:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public double Summ { get; private set; }
Or with fluent mapping:
modelBuilder.Entity<Income>().Property(t => t.Summ)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed)
As suggested by Matija Grcic and in a comment, it's a good idea to make the property private set, because you'd probably never want to set it in application code. Entity Framework has no problems with private setters.
Note: For EF .NET Core you should to use ValueGeneratedOnAddOrUpdate because HasDatabaseGeneratedOption doesnt exists, e.g.:
modelBuilder.Entity<Income>().Property(t => t.Summ)
.ValueGeneratedOnAddOrUpdate()
As of 2019, EF core allows you to have computed columns in a clean way with the fluent API:
Suppose that DisplayName is the computed column you want to define, you have to define the property as usual, possibly with a private property accessor to prevent assigning it
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
// this will be computed
public string DisplayName { get; private set; }
}
Then, in the model builder, address it with the column definition:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.Property(p => p.DisplayName)
// here is the computed query definition
.HasComputedColumnSql("[LastName] + ', ' + [FirstName]");
}
For further information, have a look at MSDN.
public string ChargePointText { get; set; }
public class FirstTable
{
[Key]
public int UserID { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public string Summ
{
get { return /* do your sum here */ }
private set { /* needed for EF */ }
}
}
References:
Bug in EF 4.1 DatabaseGeneratedOption.Computed
Calculated Columns in Entity Framework Code First Migrations
Working with Computed Columns
In EF6, you can just configure the mapping setting to ignore a calculated property, like this:
Define the calculation on the get property of your model:
public class Person
{
// ...
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName => $"{FirstName} {LastName}";
}
Then set it to ignore on the model configuration
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//...
modelBuilder.Entity<Person>().Ignore(x => x.FullName)
}
One way is doing it with LINQ:
var userID = 1; // your ID
var income = dataContext.Income.First(i => i.UserID == userID);
var outcome = dataContext.Outcome.First(o => o.UserID == userID);
var summ = income.inSumm - outcome.outSumm;
You may do it within your POCO object public class FirstTable, but I would not suggest to, because I think it's not good design.
Another way would be using a SQL view. You can read a view like a table with Entity Framework. And within the view code, you may do calculations or whatever you want. Just create a view like
-- not tested
SELECT FirstTable.UserID, Income.inCome - Outcome.outCome
FROM FirstTable INNER JOIN Income
ON FirstTable.UserID = Income.UserID
INNER JOIN Outcome
ON FirstTable.UserID = Outcome.UserID
I would go about this by just using a view model. For example rather than have the FirstTable class as a db entity would you not be better just having a view model class called FirstTable and then have a function that is used to return this class that would include the calculated sum? For example your class would just be:
public class FirstTable {
public int UserID { get; set; }
public double Sum { get; set; }
}
And then you would have a function that you call that returns the calculated sum:
public FirsTable GetNetSumByUserID(int UserId)
{
double income = dbcontext.Income.Where(g => g.UserID == UserId).Select(f => f.inSum);
double expenses = dbcontext.Outcome.Where(g => g.UserID == UserId).Select(f => f.outSum);
double sum = (income - expense);
FirstTable _FirsTable = new FirstTable{ UserID = UserId, Sum = sum};
return _FirstTable;
}
Basically the same as an SQL view and as #Linus mentioned I don't think it would be a good idea keeping the computed value in the database. Just some thoughts.
I stumbled across this question when trying to have an EF Code First model with a string column "Slug", be derived from another string column "Name". The approach I took was slightly different but worked out well so I will share it here.
private string _name;
public string Name
{
get { return _name; }
set
{
_slug = value.ToUrlSlug(); // the magic happens here
_name = value; // but don't forget to set your name too!
}
}
public string Slug { get; private set; }
What is nice about this approach is you get the automatic slug generation, while never exposing the slug setter. The .ToUrlSlug() method isn't the important part of this post, you could use anything in its place to do the work you need done. Cheers!

Categories

Resources