I am trying to query objects from a database, loop through them and check if a column has a value and, if it does not, create a value and assign it to that column and save it to the database. The problem I'm having is that the entity is detaching after the query so I cannot save the changes. Below is the code I am using to query and update the entity.
DateTime runTime = passedDateTime ?? DateTime.Now;
await using DiscordDatabaseContext database = new();
DateTime startOfWeek = exactlyOneWeek ? runTime.OneWeekAgo() : runTime.StartOfWeek(StartOfWeek);
//Add if not in a Weekly Playlist already and if the video was submitted after the start of the week
List<PlaylistData> pld = await database.PlaylistsAdded.Select(playlist => new PlaylistData
{
PlaylistId = playlist.PlaylistId,
WeeklyPlaylistID = playlist.WeeklyPlaylistID,
Videos = playlist.Videos.Where(
video => (video.WeeklyPlaylistItemId == null ||
video.WeeklyPlaylistItemId.Length == 0) &&
startOfWeek <= video.TimeSubmitted)
.Select(video => new VideoData
{
WeeklyPlaylistItemId = video.WeeklyPlaylistItemId,
VideoId = video.VideoId
}).ToList()
}).ToListAsync().ConfigureAwait(false);
int count = 0;
int nRows = 0;
foreach (PlaylistData playlistData in pld)
{
if (string.IsNullOrEmpty(playlistData.WeeklyPlaylistID))
{
playlistData.WeeklyPlaylistID = await YoutubeAPIs.Instance.MakeWeeklyPlaylist().ConfigureAwait(false);
}
foreach (VideoData videoData in playlistData.Videos)
{
PlaylistItem playlistItem = await YoutubeAPIs.Instance.AddToPlaylist(videoData.VideoId, playlistId: playlistData.WeeklyPlaylistID, makeNewPlaylistOnError: false).ConfigureAwait(false);
videoData.WeeklyPlaylistItemId = playlistItem.Id;
++count;
}
}
nRows += await database.SaveChangesAsync().ConfigureAwait(false);
The query works correctly, I get all relevant Playlist and Video Rows to work with, they have the right data in only the specified columns, and the query that is logged looks good, but saves do not work and calling database.Entry() on any of the Playlists or Video objects show that they are all detached. What am I doing wrong? Are collections saved a different way? Should my query be changed? Is there a setting on initialization that should be changed? (The only setting I have set on init that I feel like may affect this is .UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery) but the query logged isn't even split as far as I can see)
You work with projected objects
PlaylistData
VideoData
Projected objects does not tracked by EF core as far as I know. So the solution is to select DbSet's entity objects (mean types that specified in database.PlaylistsAdded and playlist.Videos properties) or select those objects before update and then update them.
UPDATE:
Example code for second option:
foreach (PlaylistData playlistData in pld)
{
var playlist = database.PlaylistsAdded
.Include(x=> x.Videos)
.First(x => x.PlaylistId == playlistData.playlistData);
if (string.IsNullOrEmpty(playlistData.WeeklyPlaylistID))
{
playlist.WeeklyPlaylistID = await YoutubeAPIs.Instance.MakeWeeklyPlaylist().ConfigureAwait(false);
}
foreach (VideoData videoData in playlistData.Videos)
{
var video = playlist.Videos.First(x=> x.VideoId == videoData.VideoId);
PlaylistItem playlistItem = await YoutubeAPIs.Instance.AddToPlaylist(videoData.VideoId, playlistId: playlistData.WeeklyPlaylistID, makeNewPlaylistOnError: false).ConfigureAwait(false);
video.WeeklyPlaylistItemId = playlistItem.Id;
++count;
}
}
NOTICE: this would produce double select's so first option is more preferred
Related
I have this method that saves an entity with its related items (many-to-many relationship),
private static void Save<T>(TbCommonHistoryLog log, List<T> lstDetails) where T : IHasSerial
{
foreach (var item in lstDetails.OrderBy(x => x.Serial))
{
var ser = SerializeObject(item);
var record = oContext.TbHistoryLog_Lists.FirstOrDefault(x => x.ListObjectJson == ser);
if (record == null) //add new list item
{
TbCommonHistoryLog_Lists listObject = new TbCommonHistoryLog_Lists()
{
ListObjectJson = SerializeObject(item)
};
var details = new TbCommonHistoryLogDetails { TbHistoryLog = log, TbHistoryLog_Lists = listObject };
oContext.TbHistoryLogDetails.Add(details);
}
else //attach an existing list item
{
var o = oContext.TbHistoryLog_Lists.Find(record.Id);
oContext.TbHistoryLog_Lists.Attach(o);
var details = new TbCommonHistoryLogDetails { TbHistoryLog = log, TbHistoryLog_Lists = o };
oContext.TbHistoryLogDetails.Add(details);
}
}
oContext.BulkSaveChanges();
}
I have two tables: TbCommonHistoryLog, TbCommonHistoryLog_Lists, that are in many to many relationship, the joining table is TbCommonHistoryLogDetails,
What I'm doing here is an auditing for master-detail models, all audits are serialized to JSON in DB, I save the head object in the TbCommonHistoryLog table, and every list item in the TbHistoryLog_Lists table, in the mthod above I check if the list item is already exists in the database or not to avoid duplicating.
but this process takes more than 15 seconds which is a very long time, I can't figure out what am I doing wrong here.. please help?
For every single item in collection you're querying database. My suggestion is to save records in var, then ask the variable if the item is in database.
var databaseRecords = oContext.TbHistoryLog_Lists.ToList();
Then in the loop:
var record = databaseRecords.FirstOrDefault(x => x.ListObjectJson == ser);
Problem:
Every time I add a new record, it overwrites the last record, thus after looping through, always leaves the table with 1 record.
List<Accounting_InvoiceCheck> liIC = new List<Accounting_InvoiceCheck>();
foreach (Accounting_Invoice invoice in liInvoices.OrderBy(x => x.EntityID).ThenBy(x => x.VendorID))
{
Accounting_InvoiceCheck ic = new Accounting_InvoiceCheck();
ic = invoiceMaster.Check;
ctxAcct.Accounting_InvoiceCheck.add(ic);
//ctxAcct.Entry(ic).State = EntityState.Added
}
ctxAcct.SaveChanges()
The Above code only takes the last record, so the count is always 1 no matter what. Very frustrating when I see everybody else's code online doing just that.
Synopsis
I am using Entity Framework 6 in an MVC5 application. So I have 2 tables that are being updated when I call a method. The table names are Invoices and InvoiceChecks. The relation is, Invoices is the main table, and when you pay an invoice, you create a record in InvoiceChecks.
Below the code is trying to add it in 2 ways (both are there for your purpose to see). One is to create a list of InvoiceChecks, then call ctx.AddRange(liIC) which leaves the table with only the last item in liIC. Another way I've tried is looping through and calling ctx.entry(ic).state = EntityState.Added which is overriding the last row every time it is called.
Any ideas as to why, no matter how I add, the last row is always overwritten?
List<Accounting_InvoiceCheck> liIC = new List<Accounting_InvoiceCheck>();
foreach (Accounting_Invoice invoice in liInvoices.OrderBy(x => x.EntityID).ThenBy(x => x.VendorID))
{
Accounting_InvoiceCheck ic = new Accounting_InvoiceCheck();
ic = invoiceMaster.Check;
ic.InvoiceID = invoice.ID;
var sumAmt = ctx.Accounting_InvoiceChecks.Where(x => x.InvoiceID == invoice.ID).GroupBy(x => x.InvoiceID).Select(x => new { TransAmt = x.Sum(s => s.CKAmt) });
decimal amt = 0;
if (sumAmt.ToList().Count > 0)
{
amt = sumAmt.FirstOrDefault().TransAmt;
}
amt += invoice.AmtPaying;
ctx.Entry(invoice).Entity.AmtPaying = ic.TransAmt - amt;
ctx.Entry(invoice).State = EntityState.Modified;
ctx.Entry(ic).State = EntityState.Added;
//ctx.Accounting_InvoiceChecks.Add(ic);
//liIC.Add(ic);
}
ctx.Accounting_InvoiceChecks.AddRange(liIC);
ctx.SaveChanges();
Updated:
So I discovered that the below code adds it correctly. However, my question is why does the below code work and ic = new Accounting_InvoiceCheck() at the beginning of the foreach loop not work?
ctxAcct.Accounting_InvoiceChecks.Add(new Accounting_InvoiceCheck()
{
InvoiceID = ic.InvoiceID,
AcctPeriod = ic.AcctPeriod,
BankID = ic.BankID,
CheckDate = ic.CheckDate,
CheckNo = ic.CheckNo,
CKAmt = ic.CKAmt,
Description = ic.Description,
TransAmt = ic.TransAmt,
VendorID = ic.VendorID
});
The following code results in deletions instead of updates.
My question is: is this a bug in the way I'm coding against Entity Framework or should I suspect something else?
Update: I got this working, but I'm leaving the question now with both the original and the working versions in hopes that I can learn something I didn't understand about EF.
In this, the original non working code, when the database is fresh, all the additions of SearchDailySummary object succeed, but on the second time through, when my code was supposedly going to perform the update, the net result is a once again empty table in the database, i.e. this logic manages to be the equiv. of removing each entity.
//Logger.Info("Upserting SearchDailySummaries..");
using (var db = new ClientPortalContext())
{
foreach (var item in items)
{
var campaignName = item["campaign"];
var pk1 = db.SearchCampaigns.Single(c => c.SearchCampaignName == campaignName).SearchCampaignId;
var pk2 = DateTime.Parse(item["day"].Replace('-', '/'));
var source = new SearchDailySummary
{
SearchCampaignId = pk1,
Date = pk2,
Revenue = decimal.Parse(item["totalConvValue"]),
Cost = decimal.Parse(item["cost"]),
Orders = int.Parse(item["conv1PerClick"]),
Clicks = int.Parse(item["clicks"]),
Impressions = int.Parse(item["impressions"]),
CurrencyId = item["currency"] == "USD" ? 1 : -1 // NOTE: non USD (if exists) -1 for now
};
var target = db.Set<SearchDailySummary>().Find(pk1, pk2) ?? new SearchDailySummary();
if (db.Entry(target).State == EntityState.Detached)
{
db.SearchDailySummaries.Add(target);
addedCount++;
}
else
{
// TODO?: compare source and target and change the entity state to unchanged if no diff
updatedCount++;
}
AutoMapper.Mapper.Map(source, target);
itemCount++;
}
Logger.Info("Saving {0} SearchDailySummaries ({1} updates, {2} additions)", itemCount, updatedCount, addedCount);
db.SaveChanges();
}
Here is the working version (although I'm not 100% it's optimized, it's working reliably and performing fine as long as I batch it out in groups of 500 or less items in a shot - after that it slows down exponentially but I think that just may be a different question/subject)...
//Logger.Info("Upserting SearchDailySummaries..");
using (var db = new ClientPortalContext())
{
foreach (var item in items)
{
var campaignName = item["campaign"];
var pk1 = db.SearchCampaigns.Single(c => c.SearchCampaignName == campaignName).SearchCampaignId;
var pk2 = DateTime.Parse(item["day"].Replace('-', '/'));
var source = new SearchDailySummary
{
SearchCampaignId = pk1,
Date = pk2,
Revenue = decimal.Parse(item["totalConvValue"]),
Cost = decimal.Parse(item["cost"]),
Orders = int.Parse(item["conv1PerClick"]),
Clicks = int.Parse(item["clicks"]),
Impressions = int.Parse(item["impressions"]),
CurrencyId = item["currency"] == "USD" ? 1 : -1 // NOTE: non USD (if exists) -1 for now
};
var target = db.Set<SearchDailySummary>().Find(pk1, pk2);
if (target == null)
{
db.SearchDailySummaries.Add(source);
addedCount++;
}
else
{
AutoMapper.Mapper.Map(source, target);
db.Entry(target).State = EntityState.Modified;
updatedCount++;
}
itemCount++;
}
Logger.Info("Saving {0} SearchDailySummaries ({1} updates, {2} additions)", itemCount, updatedCount, addedCount);
db.SaveChanges();
}
The thing that keeps popping up in my mind is that maybe the Entry(entity) or Find(pk) method has some side effects? I should probably be consulting the documentation but any advice is appreciated..
It's a slight assumption on my part (without looking into your models/entities), but have a look at what's going on within this block (see if the objects being attached here are related to the deletions):
if (db.Entry(target).State == EntityState.Detached)
{
db.SearchDailySummaries.Add(target);
addedCount++;
}
Your detached object won't be able to use its navigation properties to locate its related objects; you'll be re-attaching an object in a potentially conflicting state (without the correct relationships).
You haven't mentioned what is being deleted above, so I may be way off. Just off out, so this is a little rushed, hope there's something useful in there.
I'm working on c# winforms Application. I want to find and Update any specific record on parse.com.
My problem is that I find the record but I don't know how to update it.
The find code is this:
int ID = Convert.ToInt32(txtId.Text);
var FindID = (from find in ParseObject.GetQuery("DriverID")
where find.Get<Int32>("DriverID") == ID
select find);
var ID = FindID.FindAsync();
Finally its working
public async void UpdateDriverOnParse(Int32 ID)
{
var query = (from find in ParseObject.GetQuery("DriverLogin")
where find.Get<Int32>("SystemID") == ID
select find);
// Retrieve the results
IEnumerable<ParseObject> Data = await query.FindAsync();
//for updating the selected row
foreach (var row in Data)
{
row["Pin"] = Convert.ToInt32(txtPinNo.Text);
row["DriverID"] = Convert.ToInt32(txtCallSign.Text);
row["Name"] = txtFirstName.Text+" "+txtMname.Text+" "+txtLastName.Text;
await row.SaveAsync();
}
}
FindAsync returns an IEnumerable. If you just want one object you can use FirstAsync. When you have your object, update it like you would any IDictionary and then call 'await ID.SaveAsync()'
I am new to asp.net, C# and sql and could use some guidance.. I am using the :below: db with the linq-to-entities framework. I need to associate a particular 'Ride' with a particular 'Vehicle' but am unsure exactly how to proceed. I have set a navigation property between the two objects but now need to let the vehicle hold a reference to a list of the rides it takes. Do I need a separate column in vehicle to hold this list of rides? Could someone please show me the syntax to accomplish this?
Here is the code I currently have, with comments at the two spots I need help:
private void setNewRide(Ride newRide, int carNum)
{
using (myEntities = new RamRideOpsEntities())
{
var assignedCar = (from car in myEntities.Vehicles
where (car.CarNum == carNum)
select car).FirstOrDefault();
if (assignedCar != null && newRide != null)
{
Ride lastRide = assignedCar.Rides.LastOrDefault(); //HERE IS WHERE I NEED TO LOAD THE MOST RECENT 'RIDE' FOR THIS CAR, IS THIS CORRECT???
if (lastRide != null)
{
lastRide.Status = "Completed";
assignedCar.numRides = assignedCar.numRides + 1;
lastRide.TimeDroppedOff = DateTime.Now;
TimeSpan duration = (lastRide.TimeDroppedOff - lastRide.TimeDispatched).Value;
lastRide.ServiceTime = duration;
if (assignedCar.AvgRideTime == null)
{
assignedCar.AvgRideTime = duration;
}
else
{
assignedCar.AvgRideTime = TimeSpan.FromSeconds(( ((TimeSpan)assignedCar.AvgRideTime).Seconds + duration.Seconds) / assignedCar.numRides);
}
}
assignedCar.Status = "EnRoute";
assignedCar.CurrPassengers = newRide.NumPatrons;
assignedCar.StartAdd = newRide.PickupAddress;
assignedCar.EndAdd = newRide.DropoffAddress;
//HERE IS WHERE I NEED TO ADD THE 'newRide' OBJECT TO THE LIST OF RIDES IN THE 'assignedCar' ..SYNTAX???
newRide.TimeDispatched = DateTime.Now;
newRide.AssignedCar = carNum;
newRide.Status = "EnRoute";
myEntities.SaveChanges();
SelectCarUP.DataBind();
SelectCarUP.Update();
}
}
}
For:
// HERE IS WHERE I NEED TO LOAD THE MOST RECENT 'RIDE' FOR THIS CAR, IS
// THIS CORRECT???
Ride lastRide = assignedCar.Rides.LastOrDefault();
Use:
Ride lastRide = assignedCar.Rides
.OrderByDescending(r => r.TimeDispatched)
.FirstOrDefault();
...and for:
// HERE IS WHERE I NEED TO ADD THE 'newRide' OBJECT TO THE LIST OF RIDES
// IN THE 'assignedCar' ..SYNTAX???
...your Vehicle entity has a Rides collection property already according to your model diagram, so you should just be able to say:
assignedCar.Rides.Add(newRide);
Finally - and purely as a matter of personal taste - instead of:
var assignedCar = (from car in myEntities.Vehicles
where (car.CarNum == carNum)
select car).FirstOrDefault();
I'd use:
var assignedCar = myEntities.Vehicles
.FirstOrDefault(car => car.CarNum == carNum);
In my opinion, the following articles are good places to start:
EF 4.1 Code First Walkthrough
Code First Relationships Fluent API. See Blog and Post entities, where Blog has a one-to-many
relationship with Posts.