Update record using linq with ADO.NET with Oracle - wpf

I have created an ADO.NET entity data model, and using linq to update/edit my oracle database.
using (Entities ent = new Entities())
{
RUSHPRIORITYRATE rp = new RUSHPRIORITYRATE();
rp.RATE = rate;
var query = from j in ent.RUSHPRIORITYRATEs
select j;
List<RUSHPRIORITYRATE> list = query.ToList();
if (list.Count == 0)
{
ent.AddToRUSHPRIORITYRATEs(rp);
ent.SaveChanges();
}
else
{
foreach (RUSHPRIORITYRATE r in query)
{
r.RATE = rp.RATE;
}
ent.SaveChanges();
}
}
I have a method that either adds or updates a Table that will always have one record. The record's value is then only update once there is one record in place. Adding to the table is no problem, but I've looked up how to update recores through MSDN, and "ent" does not seem to have the "submitchanges" method that the solution requires. Running this, I get the error: "The property 'RATE' is part of the object's key information and cannot be modified."

Related

Consulting two databases and three tables (Entity Framework, SQL Server)

I'm working on a MVC .NET application using EF.
I have a JqGrid in my view which works correctly bringing data from one table, but the information that I need is in two databases and three tables (Database is not normalized correctly, it sucks). I already have my two context (edmx) created in my project, I just had no idea on how to proceed in my Model. Here is my query which works fine in SQL Server
SELECT
a.idPlanta, a.idArticulo, b.DescripcionCorta, c.Planta,
a.FechaAlta, a.Existencia
FROM
CAT_ArticulosEnPlanta AS a
INNER JOIN
CAT_Articulos AS b ON a.idArticulo = b.idArticulo
INNER JOIN
[LEC].[dbo].[Plantas] AS c ON a.idPlanta = c.PlantaID
ORDER BY
b.DescripcionCorta ASC;
and here is my model that brings information from one table
public List<ArticulosPlantaView> GetArticulosEnPlanta()
{
List<ArticulosPlantaView> articuloss = new List<ArticulosPlantaView>();
using (TemakaSoftTestEntities TSTE = new TemakaSoftTestEntities())
{
ArticulosPlantaView APV;
foreach (CAT_ArticulosEnPlanta a in TSTE.CAT_ArticulosEnPlanta)
{
APV = new ArticulosPlantaView();
APV.idArticulo = a.idArticulo;
APV.idPlanta = a.idPlanta;
APV.idUsuarioAlta = a.idUsuarioAlta;
APV.FechaAlta = a.FechaAlta;
APV.Existencia = a.Existencia;
APV.Comentarios = a.Comentarios;
APV.idUsuarioElimina = a.idUsuarioElimina;
APV.FechaEliminacion = a.FechaEliminacion;
APV.Visible = a.Visible;
articuloss.Add(APV);
}
return articuloss;
}
}
public ArticulosPlantaDataView GetArticulosPlantaDataView()
{
ArticulosPlantaDataView APDV = new ArticulosPlantaDataView();
List<ArticulosPlantaView> articuloss = GetArticulosEnPlanta();
APDV.articulos = articuloss;
return APDV;
}
An example will help a lot.
THANK YOU VERY MUCH!!!

WebAPI EF update 30,000 rows of data is very slow

I am trying to have my asp.net WebAPI web service read a .csv and update a database using Entity Framework. The .csv file is about 20,000-30,000 rows.
As of now I am using a TextfieldParser to read the .csv, each row of the .csv file I create a new object, then add object to the EF context.
Once it's done adding all rows to the context, then I call db.SaveChanges();
Watching the console I noticed it calls an update statement for each row... which takes a long time. Is there a better more efficient way to accomplish this?
if (filetype == "xxx")
{
using (TextFieldParser csvReader = new TextFieldParser(downloadFolder + fileName))
{
csvReader.SetDelimiters(new string[] { "," });
csvReader.HasFieldsEnclosedInQuotes = true;
int rowCount = 1;
while (!csvReader.EndOfData)
{
string[] fieldData = csvReader.ReadFields();
//skip header row
if (rowCount != 1)
{
var t = new GMI_adatpos
{
PACCT = fieldData[3]
};
db.GMI_adatpos.Add(t);
}
rowCount++;
}
}
}
db.SaveChanges();
This issue is very common,
In your case, we can split it into two category:
Add vs AddRange Performance
Write & database round-trip
Add vs AddRange Performance
The Add method will try to detect change every time you add a new record while the AddRange only does it once. Detecting changes every time can take several minutes.
This issue is very easy to fix, simply create a list, add the entity to this list instead and use AddRange with the list at the end.
List<GMI_adatpo> list = new List<GMI_adatpo>();
if (filetype == "xxx")
{
using (TextFieldParser csvReader = new TextFieldParser(downloadFolder + fileName))
{
csvReader.SetDelimiters(new string[] { "," });
csvReader.HasFieldsEnclosedInQuotes = true;
int rowCount = 1;
while (!csvReader.EndOfData)
{
string[] fieldData = csvReader.ReadFields();
//skip header row
if (rowCount != 1)
{
var t = new GMI_adatpos
{
PACCT = fieldData[3]
};
list.Add(t);
}
rowCount++;
}
}
}
db.GMI_adatpos.AddRange(list)
db.SaveChanges();
Write & Database round-trip
Everytime you save a record, you perform a database round-trip. So if you insert average 30,000 record, you perform 30,000 database round-trip which is insane!
Disclaimer: I'm the owner of the project Entity Framework Extensions
This library allows to perform:
BulkSaveChanges
BulkInsert
BulkUpdate
BulkDelete
BulkMerge
You can either call BulkSaveChanges instead of SaveChanges or create a list to insert and use directly BulkInsert instead for even more performance.
BulkSaveChanges Solution (Way faster than SaveChanges)
db.GMI_adatpos.AddRange(list)
db.SaveChanges();
BulkInsert Solution (Fastest than BulkSaveChanges but do not save related entities)
db.BulkInsert(list);
Because the number of items added to the DbContext is very high, ram space is gradually filled, and operation is very slow. Therefore is better that after a few records (ex 100), calling SaveChanges Methods and renew DbContext.
if (filetype == "xxx")
{
using (TextFieldParser csvReader = new TextFieldParser(downloadFolder + fileName))
{
csvReader.SetDelimiters(new string[] { "," });
csvReader.HasFieldsEnclosedInQuotes = true;
int rowCount = 1;
while (!csvReader.EndOfData)
{
if(rowCount%100 == 0)
{
db.Dispose();
db.SaveChanges();
db = new AppDbContext();//Your DbContext
}
string[] fieldData = csvReader.ReadFields();
//skip header row
if (rowCount != 1)
{
var t = new GMI_adatpos
{
PACCT = fieldData[3]
};
db.GMI_adatpos.Add(t);
}
rowCount++;
}
}
}

Fastest way to record all DocIds and FileNames from dtSearch in SQL database

I am using dtSearch on combination with a SQL database and would like to maintain a table that includes all DocIds and their related FileNames. From there, I will add a column with my foreign key to allow me to combine text and database searches.
I have code to simply return all the records in the index and add them one by one to the DB. This, however, takes FOREVER, and doesn't address the issue of how to simply append new records as they are added to the index. But just in case it helps:
MyDatabaseContext db = new StateScapeEntities();
IndexJob ij = new dtSearch.Engine.IndexJob();
ij.IndexPath = #"d:\myindex";
IndexInfo indexInfo = dtSearch.Engine.IndexJob.GetIndexInfo(#"d:\myindex");
bool jobDone = ij.Execute();
SearchResults sr = new SearchResults();
uint n = indexInfo.DocCount;
for (int i = 1; i <= n; i++)
{
sr.AddDoc(ij.IndexPath, i, null);
}
for (int i = 1; i <= n; i++)
{
sr.GetNthDoc(i - 1);
//IndexDocument is defined elsewhere
IndexDocument id = new IndexDocument();
id.DocId = sr.CurrentItem.DocId;
id.FilePath = sr.CurrentItem.Filename;
if (id.FilePath != null)
{
db.IndexDocuments.Add(id);
db.SaveChanges();
}
}
To keep the DocId in the index you must use the flag dtsIndexKeepExistingDocIds in the IndexJob
You can also look the dtSearch Text Retrieval Engine Programmer's Reference when the DocID is changed
When a document is added to an index, it is assigned a DocId, and DocIds are always numbered sequentially.
When a document is reindexed, the old DocId is cancelled and a new DocId is assigned.
When an index is compressed, all DocIds in the index are renumbered to remove the cancelled DocIds unless the dtsIndexKeepExistingDocIds flag is set in IndexJob.
When an index is merged into another index, DocIds in the target index are never changed. The documents merged into the target index will all be assigned new, sequentially-numbered DocIds, unless (a) the dtsIndexKeepExistingDocIds flag is set in IndexJob and (b) the indexes have non-overlapping ranges of doc ids.
To improve your speed you can search for the word "xfirstword" and get all documents in an index.
You can also look to the faq How to retrieve all documents in an index
So, I used part of user2172986's response, but combined it with some additional code to get the solution to my question. I did indeed have to set the dtsKeepExistingDocIds flag in my index update routine.
From there, I only wanted to add the newly created DocIds to my SQL database. For that, I used the following code:
string indexPath = #"d:\myindex";
using (IndexJob ij = new dtSearch.Engine.IndexJob())
{
//make sure the updated index doesn't change DocIds
ij.IndexingFlags = IndexingFlags.dtsIndexKeepExistingDocIds;
ij.IndexPath = indexPath;
ij.ActionAdd = true;
ij.FoldersToIndex.Add( indexPath + "<+>");
ij.IncludeFilters.Add( "*");
bool jobDone = ij.Execute();
}
//create a DataTable to hold results
DataTable newIndexDoc = MakeTempIndexDocTable(); //this is a custom method not included in this example; just creates a DataTable with the appropriate columns
//connect to the DB;
MyDataBase db = new MyDataBase(); //again, custom code not included - link to EntityFramework entity
//get the last DocId in the DB?
int lastDbDocId = db.IndexDocuments.OrderByDescending(i => i.DocId).FirstOrDefault().DocId;
//get the last DocId in the Index
IndexInfo indexInfo = dtSearch.Engine.IndexJob.GetIndexInfo(indexPath);
uint latestIndexDocId = indexInfo.LastDocId;
//create a searchFilter
dtSearch.Engine.SearchFilter sf = new SearchFilter();
int indexId = sf.AddIndex(indexPath);
//only select new records (from one greater than the last DocId in the DB to the last DocId in the index itself
sf.SelectItems(indexId, lastDbDocId + 1, int.Parse(latestIndexDocId.ToString()), true);
using (SearchJob sj = new dtSearch.Engine.SearchJob())
{
sj.SetFilter(sf);
//return every document in the specified range (using xfirstword)
sj.Request = "xfirstword";
// Specify the path to the index to search here
sj.IndexesToSearch.Add(indexPath);
//additional flags and limits redacted for clarity
sj.Execute();
// Store the error message in the status
//redacted for clarity
SearchResults results = sj.Results;
int startIdx = 0;
int endIdx = results.Count;
if (startIdx==endIdx)
return;
for (int i = startIdx; i < endIdx; i++)
{
results.GetNthDoc(i);
IndexDocument id = new IndexDocument();
id.DocId = results.CurrentItem.DocId;
id.FileName= results.CurrentItem.Filename;
if (id.FileName!= null)
{
DataRow row = newIndexDoc.NewRow();
row["DocId"] = id.DocId;
row["FileName"] = id.FileName;
newIndexDoc.Rows.Add(row);
}
}
newIndexDoc.AcceptChanges();
//SqlBulkCopy
using (SqlConnection connection =
new SqlConnection(db.Database.Connection.ConnectionString))
{
connection.Open();
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
{
bulkCopy.DestinationTableName =
"dbo.IndexDocument";
try
{
// Write from the source to the destination.
bulkCopy.WriteToServer(newIndexDoc);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
newIndexDoc.Clear();
db.UpdateIndexDocument();
}
Here is my new solution with AddDoc method from the SearchResults interface:
First get the StartingDocID and the LastDocID from the IndexInfo and walk the loop like this:
function GetFilename(paDocID: Integer): String;
var
lCOMSearchResults: ISearchResults;
lSearchResults_Count: Integer;
begin
if Assigned(prCOMServer) then
begin
lCOMSearchResults := prCOMServer.NewSearchResults as ISearchResults;
lCOMSearchResults.AddDoc(GetIndexPath(prIndexContent), paDocID, 0);
lSearchResults_Count := lCOMSearchResults.Count;
if lSearchResults_Count = 1 then
begin
lCOMSearchResults.GetNthDoc(0);
Result := lCOMSearchResults.DocDetailItem['_Filename'];
end;
end;
end

Nicer way to add entry into main db table and link table

Hi all the below solution works in that it creates a record in the MeetingRoomRequest table and also adds the associated amenities to that request into the MeetingRoomRequestAmenityLink table. However it just feels a bit clunky, so I was wondering if there is a nicer solution out there (i.e. not having to create 2 context instances) using MVC 3 and Entity Framework??
Please note i've set up the necessary relationships (one to many) in SQL Server and Entity Framework.
Also please note AmenityList is an array of id's (e.g. [1,2,4])
private readonly IDataRepository<MeetingRoomRequest> _meetingRoomRequestRepository = new DataRepository<MeetingRoomRequest>();
private readonly IDataRepository<MeetingRoomRequestAmenityLink> _meetingRoomRequestAmenityLinkRepository = new DataRepository<MeetingRoomRequestAmenityLink>();
var meetingRoomRequestToAdd = new MeetingRoomRequest
{
User = meetingRoomRequestViewModel.User,
UserEmail = meetingRoomRequestViewModel.UserEmail,
Title = meetingRoomRequestViewModel.Title,
Comments = meetingRoomRequestViewModel.Comments,
StartDateTime = meetingRoomRequestViewModel.StartTime,
EndDateTime = meetingRoomRequestViewModel.EndTime,
RequestStatusID = (int)Enums.RequestStatus.New,
AttendeeCount = meetingRoomRequestViewModel.AttendeeCount,
AttendeeType = meetingRoomRequestViewModel.AttendeeType,
OfficeID = meetingRoomRequestViewModel.OfficeId,
LocationID = meetingRoomRequestViewModel.LocationId,
};
_meetingRoomRequestRepository.Add(meetingRoomRequestToAdd);
_meetingRoomRequestRepository.SaveChanges();
var meetingRoomRequestAdded = meetingRoomRequestToAdd;
foreach (var item in meetingRoomRequestViewModel.AmenityList)
{
var meetingRoomRequestAmenityLinkToAdd = new MeetingRoomRequestAmenityLink
{
AmenityID = item,
MeetingRoomRequestID = meetingRoomRequestAdded.MeetingRoomRequestID
};
_meetingRoomRequestAmenityLinkRepository.Add(meetingRoomRequestAmenityLinkToAdd);
_meetingRoomRequestAmenityLinkRepository.SaveChanges();
}
The way you are going about it looks right, but there are some improvements that could be made in efficiency of processing the request.
Since these are a child/parent relationship, you can create the parent entity and then attached the childern in the foreach loop before you call save changes on the parent entity. EF will automatically populate the foreign key value on the child object with the primary (or associated key) from the parent.
You can continue to use your Entity without having to save it back out to a variable. EF's object tracking will continue to track this throughout your function.
By moving the savechanges outside of the foreach loop, you are reducing the number of calls. I believe the same amount of SQL will be sent on the one final call, but you may see increases of not having the connection open/close. There may be other built in efficiencies as well from EF
The Code
var meetingRoomRequestToAdd = new MeetingRoomRequest
{
User = meetingRoomRequestViewModel.User,
UserEmail = meetingRoomRequestViewModel.UserEmail,
Title = meetingRoomRequestViewModel.Title,
Comments = meetingRoomRequestViewModel.Comments,
StartDateTime = meetingRoomRequestViewModel.StartTime,
EndDateTime = meetingRoomRequestViewModel.EndTime,
RequestStatusID = (int)Enums.RequestStatus.New,
AttendeeCount = meetingRoomRequestViewModel.AttendeeCount,
AttendeeType = meetingRoomRequestViewModel.AttendeeType,
OfficeID = meetingRoomRequestViewModel.OfficeId,
LocationID = meetingRoomRequestViewModel.LocationId,
};
_meetingRoomRequestRepository.Add(meetingRoomRequestToAdd);
foreach (var item in meetingRoomRequestViewModel.AmenityList)
{
meetingRoomRequestToAdd.MeetingRoomRequestAmenityLinks.Add(new MeetingRoomRequestAmenityLink
{
AmenityID = item
});
}
_meetingRoomRequestRepository.SaveChanges();

salesforce SOQL : query to fetch all the fields on the entity

I was going through the SOQL documentation , but couldn't find query to fetch all the field data of an entity say , Account , like
select * from Account [ SQL syntax ]
Is there a syntax like the above in SOQL to fetch all the data of account , or the only way is to list all the fields ( though there are lot of fields to be queried )
Create a map like this:
Map<String, Schema.SObjectField> fldObjMap = schema.SObjectType.Account.fields.getMap();
List<Schema.SObjectField> fldObjMapValues = fldObjMap.values();
Then you can iterate through fldObjMapValues to create a SOQL query string:
String theQuery = 'SELECT ';
for(Schema.SObjectField s : fldObjMapValues)
{
String theLabel = s.getDescribe().getLabel(); // Perhaps store this in another map
String theName = s.getDescribe().getName();
String theType = s.getDescribe().getType(); // Perhaps store this in another map
// Continue building your dynamic query string
theQuery += theName + ',';
}
// Trim last comma
theQuery = theQuery.subString(0, theQuery.length() - 1);
// Finalize query string
theQuery += ' FROM Account WHERE ... AND ... LIMIT ...';
// Make your dynamic call
Account[] accounts = Database.query(theQuery);
superfell is correct, there is no way to directly do a SELECT *. However, this little code recipe will work (well, I haven't tested it but I think it looks ok). Understandably Force.com wants a multi-tenant architecture where resources are only provisioned as explicitly needed - not easily by doing SELECT * when usually only a subset of fields are actually needed.
You have to specify the fields, if you want to build something dynamic the describeSObject call returns the metadata about all the fields for an object, so you can build the query from that.
I use the Force.com Explorer and within the schema filter you can click the checkbox next to the TableName and it will select all the fields and insert into your query window - I use this as a shortcut to typeing it all out - just copy and paste from the query window. Hope this helps.
In case anyone was looking for a C# approach, I was able to use reflection and come up with the following:
public IEnumerable<String> GetColumnsFor<T>()
{
return typeof(T).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
.Where(x => !Attribute.IsDefined(x, typeof(System.Xml.Serialization.XmlIgnoreAttribute))) // Exclude the ignored properties
.Where(x => x.DeclaringType != typeof(sObject)) // & Exclude inherited sObject propert(y/ies)
.Where(x => x.PropertyType.Namespace != typeof(Account).Namespace) // & Exclude properties storing references to other objects
.Select(x => x.Name);
}
It appears to work for the objects I've tested (and matches the columns generated by the API test). From there, it's about creating the query:
/* assume: this.server = new sForceService(); */
public IEnumerable<T> QueryAll<T>(params String[] columns)
where T : sObject
{
String soql = String.Format("SELECT {0} FROM {1}",
String.Join(", ", GetColumnsFor<T>()),
typeof(T).Name
);
this.service.QueryOptionsValue = new QueryOptions
{
batchsize = 250,
batchSizeSpecified = true
};
ICollection<T> results = new HashSet<T>();
try
{
Boolean done = false;
QueryResult queryResult = this.service.queryAll(soql);
while (!finished)
{
sObject[] records = queryResult.records;
foreach (sObject record in records)
{
T entity = entity as T;
if (entity != null)
{
results.Add(entity);
}
}
done &= queryResult.done;
if (!done)
{
queryResult = this.service.queryMode(queryResult.queryLocator);
}
}
}
catch (Exception ex)
{
throw; // your exception handling
}
return results;
}
For me it was the first time with Salesforce today and I came up with this in Java:
/**
* #param o any class that extends {#link SObject}, f.ex. Opportunity.class
* #return a list of all the objects of this type
*/
#SuppressWarnings("unchecked")
public <O extends SObject> List<O> getAll(Class<O> o) throws Exception {
// get the objectName; for example "Opportunity"
String objectName= o.getSimpleName();
// this will give us all the possible fields of this type of object
DescribeSObjectResult describeSObject = connection.describeSObject(objectName);
// making the query
String query = "SELECT ";
for (Field field : describeSObject.getFields()) { // add all the fields in the SELECT
query += field.getName() + ',';
}
// trim last comma
query = query.substring(0, query.length() - 1);
query += " FROM " + objectName;
SObject[] records = connection.query(query).getRecords();
List<O> result = new ArrayList<O>();
for (SObject record : records) {
result.add((O) record);
}
return result;
}
I used following to get complete records-
query_all("Select Id, Name From User_Profile__c")
To get complete fields of record, we have to mention those fields as mentioned here-
https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select.htm
Hope will help you !!!

Resources