Inserting Nested Objects EF Core 5 - insert-update

I have the following entities:
Batch
Samples
SampleContainers
SampleTests
A batch contains many samples. A sample contains many SampleContainers and many SampleTests.
I am trying to make a copy of batch and insert into database.
Attempt #1: get function in repository:
return await context.Set<TEntity>().FindAsync(id);
Controller:
var coc = await repository.Get(batchId);
coc.BatchStatusId = (int)Enums.BatchStatus.InProgress;
coc.IsTemplate = false;
coc.Id = 0;
var b = await repository.Add(coc);
Here only batch object was duplicated but related samples and containers were not duplicated/inserted.
Attempt #2: I changed my Get function as follows:
public async override Task<Batch> Get(int id)
{
return await context.Set<Batch>()
.Include(p => p.Samples)
.FirstOrDefaultAsync(p => p.Id == id);
}
This time batch was duplicated but samples and containers and tests were all updated with new batchId/FK (I wanted them all to be duplicated).
Attempt #3: following this, I implemented as follows:
public async Task<int> DuplicateBatch([FromBody]int batchId)
{
try
{
var coc = await repository.Get(batchId);
coc.BatchStatusId = (int)Enums.BatchStatus.InProgress;
coc.IsTemplate = false;
coc.Id = 0;
var samples = coc.Samples.ToList();
repository.DetachEntity(coc);
var b = await repository.Add(coc);
var allSampleTests = await sampleTestRepo.GetAll();
var allSampleContainers = await sampleContainersRepo.GetAll();
var sampletests = from st in allSampleTests
join s in samples on st.SampleId equals s.Id
select st;
var sampleContainers = from sc in allSampleContainers
join s in samples on sc.SampleId equals s.Id
select sc;
sampleRepo.DetachEntities(samples);
sampleTestRepo.DetachEntities(sampletests.ToList());
sampleContainersRepo.DetachEntities(sampleContainers.ToList());
foreach (var s in samples)
{
s.BatchId = b.Id;
var sample = await sampleRepo.Add(s);
foreach (var st in sampletests)
{
st.SampleId = sample.Id;
await sampleTestRepo.Add(st);
}
foreach(var sc in sampleContainers)
{
sc.SampleId = sample.Id;
await sampleContainersRepo.Add(sc);
}
}
return 1;
}
catch (Exception ex)
{
return 0;
}
}
This time I am facing the following exception as soon as I reach Detach function:
{"The property 'Batch.Id' is part of a key and so cannot be modified
or marked as modified. To change the principal of an existing entity
with an identifying foreign key, first delete the dependent and invoke
'SaveChanges', and then associate the dependent with the new
principal."}

This is how I did it, most of it is self explanatory.
public async Task<int> DuplicateBatch([FromBody]int batchId)
{
try
{
//STEP 1: Fetch the entities
var coc2 = await repository.Get(batchId);
var samples = coc2.Samples.ToList();
var allSampleTests = await sampleTestRepo.GetAll();
var allSampleContainers = await sampleContainersRepo.GetAll();
var sampletests = samples.SelectMany(st => st.SampleTests).ToList();
var samplecontainers = samples.SelectMany(st => st.SampleContainers).ToList();
//STEP 2: Detach
var coc = repository.DetachEntity(coc2);
var samplesDetached = sampleRepo.DetachEntities(samples);
var sampleTestsDetached = sampleTestRepo.DetachEntities(sampletests);
var sampleContianersDetached = sampleContainersRepo.DetachEntities(samplecontainers);
//STEP 3: Update object
coc2.BatchStatusId = (int)Enums.BatchStatus.InProgress;
coc2.IsTemplate = false;
var b = await repository.Add(coc);
return 1;
}
catch (Exception ex)
{
return 0;
}
}

Related

How can I put data into class object when I have to fetch this data using get request in loop?

This is my code. I need to get 30 class object(number of teams in NBA) with data. In both of API REQUEST there is "teamId". Using this "teamId" I need to fetch TEAM NAME from other requests. That's why I used loop, but this solution is to slow and app doesn't work good. I am looking for different solution. How can I connect this two api requests in the one object? Below the code I wrote.
String year = "2020";
http.Response response = await http.get(Uri.parse(
"https://api-nba-v1.p.rapidapi.com/standings/standard/$year?rapidapi-key=$kApiKey"));
var data = jsonDecode(response.body);
if (response.statusCode == 200) {
var standingsDetails = data['api']['standings'];
int standingsDetailsLength = standingsDetails.length;
List<StandingsModel> standingsModel = [];
for (int i = 0; i < standingsDetailsLength; i++) {
http.Response responseTeam = await http.get(Uri.parse(
"https://api-nba-v1.p.rapidapi.com/teams/teamId/${standingsDetails[0]['teamId']}?rapidapi-key=$kApiKey"));
var dataTeam = jsonDecode(responseTeam.body);
var teamDetails = dataTeam['api']['teams'];
StandingsModel standingModel = StandingsModel(
win: standingsDetails[i]['win'],
loss: standingsDetails[i]['loss'],
rank: standingsDetails[i]['conference']['rank'],
winPercentage: standingsDetails[i]['winPercentage'],
homeLoss: standingsDetails[i]['home']['loss'],
homeWin: standingsDetails[i]['home']['win'],
awayLoss: standingsDetails[i]['away']['loss'],
awayWin: standingsDetails[i]['away']['win'],
teamId: standingsDetails[i]['teamId'],
teamName: teamDetails[i]['fullName']);
standingsModel.add(standingModel);
}
standingsModel.sort((a, b) => b.winPercentage.compareTo(a.winPercentage));
print(standingsModel.length);
return (standingsModel);
} else {
print(response.statusCode);
}
}
The problem is because you are awaiting each individual API call in sequence, so you are waiting for each call to finish before you start the next one, which is going to take a long time depending on how much data you are retrieving and how many teams you have to loop through. A way around this is to split each "team name" API call into its own future and then await them all concurrently.
Future<List<StandingsModel>> getStandingsModels(String year) async {
http.Response response = await http.get(Uri.parse("https://api-nba-v1.p.rapidapi.com/standings/standard/$year?rapidapi-key=$kApiKey"));
final data = jsonDecode(response.body);
if (response.statusCode == 200) {
final standingsDetails = data['api']['standings'];
final futures = List<Future<StandingsModel>>();
for (var i = 0; i < standingsDetails.length; i++) {
final teamId = standingsDetail['teamId'];
futures.add(getTeamDetails(standingsDetails, i, teamId));
}
final standingsModels = await Future.wait(futures);
standingsModels.sort((a, b) => b.winPercentage.compareTo(a.winPercentage));
return standingsModels;
} else {
print(response.statusCode);
return [];
}
}
Future<StandingsModel> getTeamDetails(List<Map<String, dynamic>> standingsDetails, int i, String teamId) async {
http.Response response = await http.get(Uri.parse("https://api-nba-v1.p.rapidapi.com/teams/teamId/$teamId?rapidapi-key=$kApiKey"));
final data = jsonDecode(response.body);
final teamDetails = data['api']['teams'];
return StandingsModel(
win: standingsDetails[i]['win'],
loss: standingsDetails[i]['loss'],
rank: standingsDetails[i]['conference']['rank'],
winPercentage: standingsDetails[i]['winPercentage'],
homeLoss: standingsDetails[i]['home']['loss'],
homeWin: standingsDetails[i]['home']['win'],
awayLoss: standingsDetails[i]['away']['loss'],
awayWin: standingsDetails[i]['away']['win'],
teamId: standingsDetails[i]['teamId'],
teamName: teamDetails[i]['fullName'],
);
}

Convert EntityFramework to Raw SQL Queries in MVC

I am trying to make a crud calendar in my .net, my question is, How do make the below entity framework codes to SQL queries?
[HttpPost]
public JsonResult SaveEvent(Event e)
{
var status = false;
using (MyDatabaseEntities dc = new MyDatabaseEntities())
{
if (e.EventID > 0)
{
//Update the event
var v = dc.Events.Where(a => a.EventID == e.EventID).FirstOrDefault();
if (v != null)
{
v.Subject = e.Subject;
v.Start = e.Start;
v.End = e.End;
v.Description = e.Description;
v.IsFullDay = e.IsFullDay;
v.ThemeColor = e.ThemeColor;
}
}
else
{
dc.Events.Add(e);
}
dc.SaveChanges();
status = true;
}
return new JsonResult { Data = new { status = status } };
}
http://www.dotnetawesome.com/2017/07/curd-operation-on-fullcalendar-in-aspnet-mvc.html
Thanks guys
You can run raw query in entity framework with dc.Database.ExecuteSqlCommand() command like below:
var status = false;
using (MyDatabaseEntities dc = new MyDatabaseEntities())
{
if (e.EventID > 0)
{
dc.Database.ExecuteSqlCommand(&#"
UPDATE Events
SET Subject = {e.Subject},
Start = {e.Start},
End = {End},
Description = {Description},
IsFullDay = {IsFullDay},
ThemeColor = {ThemeColor},
WHERE EventID = {e.EventID}
IF ##ROWCOUNT = 0
INSERT INTO Events (EventID, Subject, Start, End, Description, IsFullDay, ThemeColor)
VALUES ({e.EventID}, {e.Subject}, ...)
");
status = true;
}
return new JsonResult { Data = new { status = status }
};

Problemas with API Key

I'm having some difficulties trying to access the ontologias of AgroPortal, it says my api key is not valid but I created an account and it was given to me an api key.
I'm trying to do like I did with BioPortal since the API is the same but with the BioPortal it works, my code is like this:
function getAgroPortalOntologies() {
var searchString = "http://data.agroportal.lirmm.fr/ontologies?apikey=72574b5d-b741-42a4-b449-4c1b64dda19a&display_links=false&display_context=false";
// we cache results and try to retrieve them on every new execution.
var cache = CacheService.getPrivateCache();
var text;
if (cache.get("ontologies_fragments") == null) {
text = UrlFetchApp.fetch(searchString).getContentText();
splitResultAndCache(cache, "ontologies", text);
} else {
text = getCacheResultAndMerge(cache, "ontologies");
}
var doc = JSON.parse(text);
var ontologies = doc;
var ontologyDictionary = {};
for (ontologyIndex in doc) {
var ontology = doc[ontologyIndex];
ontologyDictionary[ontology.acronym] = {"name":ontology.name, "uri":ontology["#id"]};
}
return sortOnKeys(ontologyDictionary);
}
var result2 = UrlFetchApp.fetch("http://data.agroportal.lirmm.fr/annotator", options).getContentText();
And what I did with BioPortal is very similar, I did this:
function getBioPortalOntologies() {
var searchString = "http://data.bioontology.org/ontologies?apikey=df3b13de-1ff4-4396-a183-80cc845046cb&display_links=false&display_context=false";
// we cache results and try to retrieve them on every new execution.
var cache = CacheService.getPrivateCache();
var text;
if (cache.get("ontologies_fragments") == null) {
text = UrlFetchApp.fetch(searchString).getContentText();
splitResultAndCache(cache, "ontologies", text);
} else {
text = getCacheResultAndMerge(cache, "ontologies");
}
var doc = JSON.parse(text);
var ontologies = doc;
var ontologyDictionary = {};
for (ontologyIndex in doc) {
var ontology = doc[ontologyIndex];
ontologyDictionary[ontology.acronym] = {"name":ontology.name, "uri":ontology["#id"]};
}
return sortOnKeys(ontologyDictionary);
}
var result = UrlFetchApp.fetch("http://data.bioontology.org/annotator", options).getContentText();
Can someone help me?
Thanks, my regards.

Get resolved SPUser IDs from Sharepoint 2010 PeoplePicker

I try to get selected user IDs from people picker control as below:
function GetUserIdsFromPP() {
var xml = _picker.find('div#divEntityData');
var visiblefor = new Array();
xml.each(function (i, row) {
var data = $(this).children().first('div').attr('data');
var xmlDoc;
if (window.DOMParser) {
parser = new DOMParser();
xmlDoc = parser.parseFromString(data, "text/xml");
}
else // Internet Explorer
{
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = false;
xmlDoc.loadXML(data);
}
var uid = xmlDoc.getElementsByTagName('Value')[0].firstChild.nodeValue;
visiblefor.push(uid);
});
return visiblefor;
}
The problem is that sometimes XML doesn't contain <Key>SPUserID</Key><Value>1</Value> and I get FQUN (user login with domain name).
What is the better way to resolve selected SPUserIds from PeoplePicker control?
This is how resolve emails from people picker control on client side
function GetEmailsFromPicker() {
var xml = _picker.find('div#divEntityData');
var result = new Array();
xml.each(function (i, row) {
var data = $(this).children().first('div').attr('data');
var xmlDoc;
if (window.DOMParser) {
parser = new DOMParser();
xmlDoc = parser.parseFromString(data, "text/xml");
}
else // Internet Explorer
{
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = false;
xmlDoc.loadXML(data);
}
var emailIndex = -1;
for (var i = 0; i < xmlDoc.childNodes[0].childNodes.length; i++) {
var element = xmlDoc.childNodes[0].childNodes[i];
var key = element.childNodes[0].childNodes[0].nodeValue;
if (key == 'Email') {
var uid = xmlDoc.childNodes[0].childNodes[i].childNodes[1].childNodes[0].nodeValue;
result.push({ EMail: uid });
break;
}
}
});
return result;
}
Use the above answer, but...
Replace this with an appropriate Jquery or Javascript element name.
var xml = _picker.find('div#divEntityData');

Why does this controller double the inserts when I try to archive the results of the Bing Search API?

I'm trying to archive my search results for a term by
Using the Bing API in an async controller
Inserting them into database using Entity Framework
using the Bing API and insert them into a database using entity framework. For whatever reason it is returning 50 results, but then it enters 100 results into the database.
My Controller Code:
public class DHWebServicesController : AsyncController
{
//
// GET: /WebService/
private DHContext context = new DHContext();
[HttpPost]
public void RunReportSetAsync(int id)
{
int iTotalCount = 1;
AsyncManager.OutstandingOperations.Increment(iTotalCount);
if (!context.DHSearchResults.Any(xx => xx.CityMarketComboRunID == id))
{
string strBingSearchUri = #ConfigurationManager.AppSettings["BingSearchURI"];
string strBingAccountKey = #ConfigurationManager.AppSettings["BingAccountKey"];
string strBingUserAccountKey = #ConfigurationManager.AppSettings["BingUserAccountKey"];
CityMarketComboRun cityMarketComboRun = context.CityMarketComboRuns.Include(xx => xx.CityMarketCombo).Include(xx => xx.CityMarketCombo.City).First(xx => xx.CityMarketComboRunID == id);
var bingContainer = new Bing.BingSearchContainer(new Uri(strBingSearchUri));
bingContainer.Credentials = new NetworkCredential(strBingUserAccountKey, strBingAccountKey);
// now we can build the query
Keyword keyword = context.Keywords.First();
var bingWebQuery = bingContainer.Web(keyword.Name, "en-US", "Moderate", cityMarketComboRun.CityMarketCombo.City.Latitude, cityMarketComboRun.CityMarketCombo.City.Longitude, null, null, null);
var bingWebResults = bingWebQuery.Execute();
context.Configuration.AutoDetectChangesEnabled = false;
int i = 1;
DHSearchResult dhSearchResult = new DHSearchResult();
List<DHSearchResult> lst = new List<DHSearchResult>();
var webResults = bingWebResults.ToList();
foreach (var result in webResults)
{
dhSearchResult = new DHSearchResult();
dhSearchResult.BingID = result.ID;
dhSearchResult.CityMarketComboRunID = id;
dhSearchResult.Description = result.Description;
dhSearchResult.DisplayUrl = result.DisplayUrl;
dhSearchResult.KeywordID = keyword.KeywordID;
dhSearchResult.Created = DateTime.Now;
dhSearchResult.Modified = DateTime.Now;
dhSearchResult.Title = result.Title;
dhSearchResult.Url = result.Url;
dhSearchResult.Ordinal = i;
lst.Add(dhSearchResult);
i++;
}
foreach (DHSearchResult c in lst)
{
context.DHSearchResults.Add(c);
context.SaveChanges();
}
AsyncManager.Parameters["message"] = "The total number of results was "+lst.Count+". And there are " + context.DHSearchResults.Count().ToString();
}
else
{
AsyncManager.Parameters["message"] = "You have already run this report";
}
AsyncManager.OutstandingOperations.Decrement(iTotalCount);
}
public string RunReportSetCompleted(string message)
{
string str = message;
return str;
}
}
Here is how I am calling it from my asp.net mvc 4 page.
#Ajax.ActionLink("Run Report", "GatherKeywordsFromBing", "DHWebServices",
new { id=item.CityMarketComboRunID},
new AjaxOptions { OnSuccess = "ShowNotifier();", UpdateTargetId = "TopNotifierMessage", HttpMethod = "POST", InsertionMode = InsertionMode.Replace, LoadingElementId = strCityMarketComboProgressID, LoadingElementDuration = 1000 },
new { #class = "ViewLink" })
<span class="ProgressIndicator" id="#strCityMarketComboProgressID"><img src="#Url.Content("~/Content/img/SmallBall.gif")" alt="loading" /></span>
For whatever reason all of
Try saving only once:
foreach (DHSearchResult c in lst)
{
context.DHSearchResults.Add(c);
}
context.SaveChanges();
Also there's nothing asynchronous in your code, so there's no point of using asynchronous controller. Not only that it won't improve anything but it might make things worse.