preventing new data snapshot from creating additional child - c#

I am trying to send data snapshots to Firebase database. Which is mostly working fine, but I am having an issue where, instead of child objects being attached directly to their intended parents, they are being added to an additional child that is attached to the intended parent.
public void CreateCampaign()
{
campaignName = campaignNameText.text;
ownerName = pInfo.userName;
if (string.IsNullOrEmpty(campaignName))
{
DebugLog("invalid campaign name.");
return;
}
DebugLog(String.Format("Attempting to add campaign ", campaignName, ownerName));
DatabaseReference reference = FirebaseDatabase.DefaultInstance.GetReference("Users").Child(pInfo.userName).Child("Campaigns").Push();
DebugLog("Running Transaction...");
reference.RunTransaction(AddCampaignTransaction)
.ContinueWith(task =>
{
if (task.Exception != null)
{
DebugLog(task.Exception.ToString());
}
else if (task.IsCompleted)
{
DebugLog("Campaign " + campaignName + " added successfully.");
}
});
}
TransactionResult AddCampaignTransaction(MutableData mutableData)
{
List<object> Campaigns = mutableData.Value as List<object>;
if (Campaigns == null)
{
Campaigns = new List<object>();
}
Dictionary<string, object> newCampaignMap = new Dictionary<string, object>();
newCampaignMap["CampaignName"] = campaignName;
newCampaignMap["Owner"] = pInfo.userName;
newCampaignMap["Members"] = 0;
Campaigns.Add(Child(newCampaignMap));
mutableData.Value = Campaigns;
return TransactionResult.Success(mutableData);
InitializeCampaign();
}
So with this all my data is added to the database, however my data structure looks like this.
Users
User Name
Campaigns
pushID
0
Campaign Name
Owner Name
Memebrs
What i need to know is; how can I prevent the child "0" from being placed between pushID and the three keys I'm adding, so that my data structure looks like this.
Users
User Name
Campaigns
pushID
Campaign Name
Owner Name
Members

Like mentioned in Gazihan's answer, the problem arises from uploading a list (Campaigns) to the database and not the node you desire (newCampaignMap).
Transactions are used where you need to modify data that already exists on the server in a ordered fashion. Because of your use of Push() to generate a new database reference, this is not needed and SetValueAsync can be used instead.
In your code for AddCampaignTransaction above, you generate an empty list (because there is never existing data), add the value of newCampaignMap to that list and then upload the list to the database instead of just the new value. In this function, you are also making use of shared internal variables from the CreateCampaign function which is bad practice, especially when dealing with asynchronous code.
Instead, I propose using the following code:
public void CreateCampaign()
{
// take local copies of values
string campaignName = campaignNameText.text;
string ownerName = pInfo.userName;
if (string.IsNullOrEmpty(campaignName))
{
DebugLog("invalid campaign name.");
return;
}
DebugLog(String.Format("Attempting to add campaign '{0}' for '{1}'... ", campaignName, ownerName));
// structure data
Dictionary<string, object> newCampaignMap = new Dictionary<string, object>();
newCampaignMap["CampaignName"] = campaignName;
newCampaignMap["Owner"] = pInfo.userName;
newCampaignMap["Members"] = 0;
DebugLog("Adding to database... ");
// get reference and upload data
DatabaseReference reference = FirebaseDatabase.DefaultInstance.GetReference("Users").Child(pInfo.userName).Child("Campaigns").Push();
reference.SetValueAsync(newCampaignMap)
.ContinueWith(task =>
{
if (task.IsFaulted)
{
DebugLog(task.Exception.ToString());
return;
}
DebugLog("Campaign " + campaignName + " added successfully.");
});
}

You are pushing a List with a single element. The 0 signifies that it is the 0th element. If there were more elements in the List, you would see 1, 2, etc.
You should replace this line
mutableData.Value = Campaigns;
with this line
mutableData.Value = newCampaignMap;
and try again.
Also you can get rid of the Campaigns etc. that are not used anymore.

Related

Adding to or updating an entity in a foreach loop takes too long time before calling SaveChanges()?

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);

The property 'ID" is part of the object's key on INSERT

We have to transfer data from one database to another. So I tried to write a program, which reads tables from the old database, create Entities and store them afterwards in the new database. At the beginning it worked very good. I tried to read only one table and transfer it to the new one. Now i receive the following error:
"The property 'Id' is part of the object's key information and cannot
be modified.
No I dont get rid of that error. Even if I try to get back to the first implementation (which worked like a charm).Here I have the definition of the Table:
Table definition
And here the code:
class MappingUtility
{
public static IEnumerable<Nation> MapNation(DataTable table, IModelFactoryService service)
{
IEnumerable<DataRow> rows = table.AsEnumerable();
Nation nat = service.Create<Nation>();
foreach(var nation in rows)
{
nat.Id = (System.Guid)nation.ItemArray[0];
nat.HVCode = (string)nation.ItemArray[1];
nat.Kurzbezeichung = (string)nation.ItemArray[2];
nat.KFZ = (string)nation.ItemArray[3];
nat.iso_a2 = (string)nation.ItemArray[4];
nat.iso_a3 = (string)nation.ItemArray[5];
nat.iso_n3 = (string)nation.ItemArray[6];
nat.Vollbezeichung = (string)nation.ItemArray[7];
nat.Updated = DateTime.Now;
nat.Created = DateTime.Now;
yield return nat;
}
}
}
using (var da = new SqlDataAdapter("SELECT * FROM NATION", "....."))
{
var table = new DataTable();
da.Fill(table);
using (var context = new DAtenbankContext())
{
int i = 0;
foreach (var nation in MappingUtility.MapNation(table, ef6))
{
Debug.WriteLine(i++);
if (context.Nation.Where(p => p.Id == nation.Id).FirstOrDefault() == null)
{
try
{
context.Entry(nation).State = EntityState.Added;
context.SaveChanges();
}
catch(DbEntityValidationException ex)
{
Debug.WriteLine("");
}
catch (DbUpdateException ex)
{
Debug.WriteLine("There where some duplicate columns in the old table.");
Debug.WriteLine(ex.StackTrace);
}
}
}
}
}
Note: The id is not autogenerated. If I try to create only one Nation at a time i can insert it. Even with this for loop I insert one nation, at the second iteration I get the error.
I suspect that you're operating on the same instance of Nation with every iteration of the loop. It appears that you only ever create one instance and then modify it over time. Entity Framework is trying to track that instance, so modifying the key is confusing it.
Move the instantiation into the loop so that you're creating new instances:
IEnumerable<DataRow> rows = table.AsEnumerable();
foreach(var nation in rows)
{
Nation nat = service.Create<Nation>();
// ...
yield return nat;
}

How can I edit or add to a particular field without pull the all object

How I can do just this ( a.myFavorits.Add()) without pulling the all object to var a , because a has a lot of data, and I don't want to pull all a object, but I can't find a way do do it.
I want to do the lambada and the linq without return something but linq is always return something
public static void addFavorits(long f,long idUser)
{
using (var db = dataBase())
{
// here i pull object user from users table
var a = db.users.Where(c => c.id == idUser).SingleOrDefault();
// here i adding to the object field myFavorits new value
//myFavorits is also a table of entitys that connected to user object
a.myFavorits.Add(new BE.FavoritsUsersLong { myLong = f });
db.SaveChanges();
}
}
I thought to do something like this but i dont know how to set the field users_TableId that is the key that connect the 2 tables
public static void addFavorits(long favoritId,long idUser)
{
using (var db = dataBase())
{
db.favoritsUsersLong.Add(new BE.FavoritsUsersLong {myLong = favoritId}
/*,users_TableId =idUser*/);
db.SaveChanges();
}
}
Here's a concrete example that does what you want. In this example, only the Name of a Company is modified and saved. Or an item is added to one of its collections.
var cmp = new Company{ CmpId = 1, Name = "Cmp1" }; // CmpId is the primary key
db.Companies.Attach(cmp);
db.Entry(cmp).Property(c => c.Name).IsModified = true;
// Or add an entity to a collection:
cmp.Users = new[] {new User { Name = "a1", PassWord = "a1" } };
try
{
db.Configuration.ValidateOnSaveEnabled = false;
db.SaveChanges();
}
finally
{
db.Configuration.ValidateOnSaveEnabled = true;
}
Result in SQL:
DECLARE #0 VarChar(30) = 'Cmp1'
DECLARE #1 Int = 1
UPDATE [dbo].[Company]
SET [Name] = #0
WHERE ([CmpId] = #1)
There are a few things to note here:
Obviously you need to know the Id of the entity you want to modify.
The object you create is called a stub entity, which is an incomplete entity. When you try to save such an entity, EF is very likely to complain about null values in required properties. That's why almost certain you'd have to disable validation (temporarily, or, better, dispose the context immediately).
If you want to add an item to a collection, you should leave validation enabled, because you'd want to know for sure that the new entity is valid. So you shouldn't mix these two ways to use a stub entity.
If you often need roughly the same small part of your entity you may consider table splitting.
I'm guessing this is what you want? I don't see you 'editting' I only see you adding.
using (var db = dataBase())
{
var a = new user();
....
//set properties etc..
...
a.myFavorits.Add(new BE.FavoritsUsersLong { myLong = f });
db.users.Add(a);
db.SaveChanges();
}

Best Way to Cache DB Lookup Tables?

I am looking to find the best way to cache the DB Lookup Tables which consists of about 75 table.
I want to cache these tables data to use them in my application so I won't open a connection with the DB each time I need them.
Here is what I am doing:
I have created a static class which contains static properties to each lookup table called MyApplicationCache.
In each property getter I am filling it with the intended data from DB.
I'm caching the data using HttpRuntime.Cache["PropertyName"]
Each time i GET this lookup table data I check if the HttpRuntime.Cache["PropertyName"] != null
If yes then I am getting it from cache else I am getting it from DB
Finally, I am invoking all properties at application start event in global.asax
Until now everything is good, but recently I've faced a performance issue and I can't solve it. If I wanted the cache object (Payer) to be updated from DB I am doing this:
MyApplicationCache.Payer = null;
This sets HttpRuntime.Cache["Payer"] = null so if I requested it again it reloads it from the DB.
list<payer> payerList = MyApplicationCache.Payer;
Now the Performance problem raises:
PayerList in DB are about 1700 record.
Each payer object has a List property called PayerBranches which requires looping on all PayerList List and getting PayerBranches for each PayerList item.
// MyApplicationCache Payer Property:
public static List<LDM.DataEntityTier.Payer> Payer {
get {
if (HttpRuntime.Cache["Payer"] != null)
return (List<LDM.DataEntityTier.Payer>)HttpRuntime.Cache["Payer"];
// request item from its original source
using (LDM.DataAccess.OracleManager OracleManager = new LDM.DataAccess.OracleManager()) {
OracleManager.OpenConnection();
List<LDM.DataEntityTier.Payer> result = new LDM.DataService.PayerService().GetPayersListWithFullName(3, OracleManager, "UTC");
//List<LDM.DataEntityTier.Payer> result = new LDM.DataService.PayerService().GetListOfPayer("Order by Name asc", OracleManager ,"UTC");
List<PayerBranches> payerBranchesList = new LDM.DataService.PayerBranchesService().GetListOfObject(OracleManager, "UTC");
OracleManager.CloseConnection();
foreach (Payer payerItem in result) {
payerItem.PayerBranches = new List<PayerBranches>();
foreach (PayerBranches item in payerBranchesList.FindAll(x => x.PayerID == payerItem.Id)) {
payerItem.PayerBranches.Add(item);
}
}
// add item to cache
HttpRuntime.Cache["Payer"] = result;
return result;
}
}
set {
if (value == null) {
HttpRuntime.Cache.Remove("Payer");
}
}
}
This problem occurs with each property that has a list in it
I don't know if there is a better way to cache data or if there is a problem in my code.
Is there is a better way to do caching?

Entity framework inserts wrong entity into db on savechanges

I am trying to write a program to scan a directory containing tv show folders, look up some details about the shows using tvrage API and then save the details to a database using entity framework.
My TVShow table pkey is the same value as taken from the tvrage database show id, and I am having issues when duplicate or similar folder names are returning the same Show info. In a situation where I have a directory containing three folders, "Alias", "Alias 1" , "Band of Brothers" I get the following output from my code
* TV SHOWS *
Alias....... NO MATCH......ADDING........DONE
Alias 1 ...... NO MATCH.....ADDING....CANT ADD, ID ALREADY EXISTS IN DB
Band of Brothers ...... NO MATCH..ADDING....
Before getting an UpdateException on the context.SaveChanges(); line
Violation of PRIMARY KEY constraint 'PK_TVShows'.
I can see using SQL profiler that the problem is that my app is trying to perform an insert on the alias show for a second time with duplicate key, but I can't see why. When I step through the code on the second interaction of the foreach loop (second "alias" folder), the code to save the show entity to the database is bypassed.
It is only on the next iteration of the foreach loop when I have created a new TVShow entity for "Band of Brothers" do I
actually reach the code which adds a Tvshow to context and saves, at which point the app crashes. In visual studio I can see
at the point of the crash that;
"show" entity in context.TVShows.AddObject(show) is "Band of Brothers" w/ a unique ID
context.TVShows only contains one record, the first Alias Entity
But SQL profiler shows that EntityFramework is instead inserting Alias for a second time, and I am stumped by why this is
private void ScanForTVShowFolders( GenreDirectoryInfo drive ) {
IEnumerable<DirectoryInfo> shows = drive.DirInfo.EnumerateDirectories();
foreach (DirectoryInfo d in shows) {
//showList contains a list of existing TV show names previously queried out of DB
if (showList.Contains(d.Name)) {
System.Console.WriteLine(d.Name + ".....MATCH");
} else {
System.Console.Write(d.Name + "......NO MATCH..ADDING....");
TVShow show = LookUpShowOnline(d.Name, drive.GenreName);
if (show.Id == -1) { // id of -1 means online search failed
System.Console.Write("..........CANT FIND SHOW" + Environment.NewLine);
} else if (context.TVShows.Any(a => a.Id == show.Id)) { //catch duplicate primary key insert
System.Console.Write(".......CANT ADD, ID ALREADY EXISTS IN DB" + Environment.NewLine);
} else {
context.TVShows.AddObject(show);
context.SaveChanges();
System.Console.Write("....DONE" + Environment.NewLine);
}
}
}
private TVShow LookUpShowOnline( string name, string genre ) {
string xmlPath = String.Format("http://services.tvrage.com/feeds/search.php?show='{0}'", name);
TVShow aShow = new TVShow();
aShow.Id = -1; // -1 = Can't find
XmlDocument xmlResp = new XmlDocument();
try { xmlResp.Load(xmlPath); } catch (WebException e) { System.Console.WriteLine(e); }
XmlNode root = xmlResp.FirstChild;
if (root.NodeType == XmlNodeType.XmlDeclaration) { root = root.NextSibling; }
XmlNode tvShowXML;
//if (showXML["episode"] == null)
// return false;
tvShowXML = root["show"];
if (tvShowXML != null) {
aShow.Id = System.Convert.ToInt16(tvShowXML["showid"].InnerText);
aShow.Name = tvShowXML["name"].InnerText.Trim();
aShow.StartYear = tvShowXML["started"].InnerText.Trim();
aShow.Status = tvShowXML["status"].InnerText.Trim();
aShow.TVGenre = context.TVGenres.Where(b => b.Name.Trim() == genre).Single();
}
return aShow;
}
}
Edit
Doing some more reading I added context.ObjectStateManager to my debug watchlist and I can see everytime I create a new TVShow entity a new record is added to _addedEntityStore. Actually if I remove context.TVShows.AddObject(show) the code still updates the database so manually adding to the context seems redundant.
If your are inserting object by foreach loop > better to keep the Primary Key outside and make it increment!
eg: int newID= Shows.Select(d=>d.Id).Max();
foreach(............)
{
show.Id = newID++;
.
.
. //remaining fields
.
context.TVShows.AddObject(show);
}
context.SaveChanges();
it works for me...!!
Turns out context.TVShows.AddObject(show) is unnecessary in my case, I was inadvertently adding all created show entities to the context when this query runs
aShow.TVGenre = context.TVGenres.Where(b => b.Name.Trim() == genre).Single();
This is not what I wanted, I just wanted to create the object, then decide whether to add it. Will be pretty easy to fix now I know why it's happening.

Categories

Resources