Linq-To-Sql ORM Update One To many - c#

I am struggling for updating several records at the same time using Linq-to-sql Orm.
The database i have made is as follows:
One Tendering has one or many details.
one TenderingDetails has one or many DetailsOptions.
One Option has One Or Many ConfigDetail.
I was able to create those normally using Linq To Sql.
but when i try to Update i end up having multiple records.
I am having a model class for each of the aforementioned entities, then i pass to the service that update the enitity of Tindering, TinderingDetails, DetailsOptions and OptionConfigDetails
then when i try to Update as follow:
the parameters are :
TenderingModel tenderingModel,
TenderingDetailsModel tenderingDetailModel,
List<TenderingOption> optionsList
try
{
var entity = ModelMapper.GetTenderingEntity(TenderingModel);
//The next statement is where i got form dataconetxt ->singleordefault
//then map the properties one by one
_unitOfWork.TenderingRepository.Update(entity);
var tenderingDetailEntity = ModelMapper.GetTenderingDetailEntity(tenderingDetailModel)
_unitOfWork.TenderingDetailsRepository.Update(tenderingDetailEntity);
//Update Options
if (optionsList != null)
{
var optionsEntities = new List<OptionItem>();
optionsList.ForEach(o =>
{
//Just get the Entity from the model
optionsEntities.Add(o.GetEntity());
});
foreach (var item in optionsEntities)
{
item.ID_Tendering_DETAILS = pricingDetailEntity.ID_Tendering_DETAILS;
//Delete the exisiting and config details
//delete all configuration details
_unitOfWork.OptionConfigurationsRepository.DeleteAllByOptionId(item.ID_OPTION);
//Add the new Config Details
item.OptionConfigurationDetails.ToList().ForEach(od =>
{
_unitOfWork.OptionConfigurationsRepository.Create(od);
});
_unitOfWork.OptionsRepository.Update(item);
}
}
//this mainly would submitChanges()
_unitOfWork.Commit();
}
catch (Exception e)
{
throw new Exception($"Error while Updating the Tendering, details are : {e.Message}");
}
what i was expecting is that i would be updating as follows:
TenderingDetail Entity would be updated by the values from the model,
the options which is a child to the Tendering Detail is to be updated by removing all its config details,
then add new ones along of updating the Options table itself
but what happens is that i would get is added records for the options and for the details instead of updating???
an Edit:
It seems that it is something i do wrong with the Update approach i am using, but i do not know what it is exactly :
for example the tendering table is having the following columns:
Id -> Primary Key
ClientId-> FK
Current Revision -> int value, nullable
some other column values that are having default values , like time stamps , and etc,
How i do the Update is, like :
public void Update(Tendering tendering)
{
var tenderingEntity = GetTendering ById(pricing.ID_Tendering);
tenderingEntity .CURRENT_REVISION = tendering.CURRENT_REVISION;
tenderingEntity .ID_CLIENT = tendering.ID_CLIENT;
}

From the code supplied, I'm not sure what _unitOfWork is here or how you're handling your db context. Typically when duplicate records are saved, it happens when different contexts are used to save the parent and child items or the item was fetched from a context other than the one you are trying to use to save. Double check the implementation of your repository and unit of work to make sure that they are issuing a single Save at the end of the operation and all values are being fetched and saved by the same context instance.
As an aside, your _UnitOfWork feels more like an active record pattern than a true unit of work.

Related

EF friendly way to update child collections whose relationship is a mapping table

For some reason I can't update child collections using Entity Framework. I have the following schema which has been generated in an .edmx.
MainRecord RecordType RecordTypeMap
MainRecordID RecordTypeID RecordTypeMap
[Other fields] RecordTypeName RecordTypeID
The RecordType table holds a small list of RecordTypes, the idea is to use the mapping table.
A MainRecord can have more than one RecordType, so the RecordTypeMap table might look like the following:
MainRecordID RecordTypeID
1002 1
1002 2
1003 1
1004 2
1004 3
1004 4
The way that EF generates this is that it knows that this is a mapping table, and my MainRecord objects have a collection of RecordTypes. The problems are that the wrong information seems to go into the database when I attempt to add, edit, or delete these records. If I am adding a new MainRecord with a call like:
context.MainRecords.Add(record);
The RecordTypes for this particular MainRecord end up in the RecordType table with new RecordTypeIDs, instead of in the RecordTypeMap table with an existing RecordTypeID. I have tried a number of permutations for editing and deleting these RecordTypes from their association with the MainRecord. While I can hack a solution, isn't there an EF-friendly way to perform the mapping function above? I'm using EF6.
EDIT: How can I made the below code edit the RecordType records associated with the MainRecord? The 'Add' section of the code works fine, although why it works when the entity state is considered 'modified' for the RecordType is beyond me...
Here is the code that saves the record. If there is an obvious syntax problem it is a transcription error.
public bool SaveRecord(MainRecord record)
{
using (var context = new DBEntities())
{
var existingRecord = context.MainRecords.SingleOrDefault(x => x.MainRecordID == record.MainRecordID);
if (existingContact != null)
{
context.Entry(existingRecord).CurrentValues.SetValues(record);
}
else
{
// Add new record.
context.MainRecords.Add(record);
// The below properly adds entries with the existing keys into the Map table.
// Not sure why this does that, it doesn't look like it would on the surface.
foreach (var type in contact.tblContactTypes)
{
context.Entry(type).State = EntityState.Modified;
}
}
}
context.SaveChanges();
return true;
}

Error in CodeFirst Seed with migrations : Modifying a column with the 'Identity' pattern is not supported. Column: 'CreatedAt'.

I have activated migrations on my Azure Mobile Services project. I filled the new seed function Inside the Configuration.cs class of the migrations. If the tables are empty, the seed function is going without any problems. When my AddorUpdate tries to update the first object I get the error in the inner exception : "Modifying a column with the 'Identity' pattern is not supported. Column: 'CreatedAt'. Table: 'CodeFirstDatabaseSchema.Category'."
Part of my code is as follows:
context.categories.AddOrUpdate(
new Category { Id="1", Code="GEN", Text="General"},
new Category { Id="2", Code="POL", Text="Politics"},
new Category { Id="3", Code="FAS", Text="Fashion"},
new Category { Id="4", Code="PEO", Text="People"},
new Category { Id="5", Code="TEC", Text="Technology"},
new Category { Id="6", Code="SPO", Text="Sport"},
new Category { Id="7", Code="LIV", Text="Living"}
);
Here's my generic implementation of Nikatlas' solution.
Short version of the answer: You can't modify CreatedAt with a null value, so you can use this function instead:
private void AddOrUpdatePreservingCreatedAt<T> (DbSet<T> set, T item) where T : EntityData
{
var existing = set.Where(i => i.Id == item.Id).FirstOrDefault();
if (existing != null)
{
item.CreatedAt = existing.CreatedAt;
}
set.AddOrUpdate(i => i.Id, item);
}
Call it like this
AddOrUpdatePreservingCreatedAt(context.YourItems, itemToBeUpdatedOrAdded);
It seems i have found a solution to this problem.
The reason this error occurs is because of the AddOrUpdate Method.
As stated in this post : http://thedatafarm.com/data-access/take-care-with-ef-4-3-addorupdate-method/
More importantly, if a match is found then the update will update all and null out any that weren’t in your AddOrUpdate.
What this means is that after the first seed, whenever your code runs it tries to update your entities correctly but it tries to pass the value null on CreatedAt field. If you look at EntityData class the CreateAt field has these attributes :
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] //Here they mark this as IDENTITY
[Index(IsClustered = true)] // Cluster index. i really dont know why ?
[TableColumn(TableColumnType.CreatedAt)]
public DateTimeOffset? CreatedAt { get; set; }
So the error occurs beacause you try to modify the CreatedAt column.
My solution was to Create a List, lookup to set CreatedAt to correct value and then addOrUpdate:
// Create List
List<Permission> permissions = new List<Permission>(new Permission[]{
new Permission { Id = "ID1" , Name = "Send SMS"},
new Permission { Id = "ID2", Name = "Send Email"} });
// Iterate through list to set CreatedAt to correct value
foreach (Permission p in permissions){
// Get the record from the db if it exists
var t = context.PermissionSet.Where(s => s.Id == p.Id).FirstOrDefault();
if (t != null){
p.CreatedAt = t.CreatedAt; //SET CreatedAt to correct Value if the record already exists
}
context.PermissionSet.AddOrUpdate(a => a.Id, p); // NOW I CAN UPDATE WITH NO PROBLEM
}
Hope this helps. :)
If you have a integer column named Id, then Entity Framework will assume that is the primary key and that it is database generated - so it is created as an IDENTITY column in the database.
You cannot specify the Id for IDENTITY columns, so you stop doing that by removing the Id = 1, Id = 2, etc
I am a bit thrown by the fact that the column you have a problem with is named "CreatedAt". It sounds like it should be a DateTime and might also be database generated, but it surely shouldn't be IDENTITY?
Anyways, the usage you probably want is the one where you specify the natural key of the entity, so that EF can identify any records that already exist. So, if CODE is the natural key then you should be writing the Seed like this:
context.categories.AddOrUpdate(
x => x.Code,//the natural key is Code
new Category { Code="GEN", Text="General"},
new Category { Code="POL", Text="Politics"},
new Category { Code="FAS", Text="Fashion"},
new Category { Code="PEO", Text="People"},
new Category { Code="TEC", Text="Technology"},
new Category { Code="SPO", Text="Sport"},
new Category { Code="LIV", Text="Living"}
);
Reference:
Take care with the AddOrUpdate method
This question, its answers and comments might help you a bit, but not much.
You can do Inserts on identity column using the solutions provided by the question. But you cannot update values of Identity Column. The only way to do updates on identity column is to mark it as not being identity. Probably by adding manual migrations.
This SO question and its answers may also be helpful.
Read here also on general MSSQL Server constraints on Updating Identity Column.
SAME PROBLEM AND SOLUTION
We had the same problem and it was a trigger in another table that had impact on the table that we had the problem on.
DETAILS OF OUR DEVELOPMENT
We developed a Xamarin App connected to an Azure Web Service. When we used the method PushAsync from iMobileServices, it gave us the error: Modifying a column with the 'Identity' pattern is not supported. Column: 'CreatedAt'.
It was strange for us as some tables did not have the problem with the web service
REASON
It seems that the trigger update got in conflict with the pushasync from the mobile device.
We disabled the trigger, switched the responsibility to the front end and it worked fine. At least for us.
We hope this soltion helps someone.

C# - Updating a row using LINQ

I am in the process of improving a console app and at the moment I cant get it to update rows instead of just creating a new row with the newer information in it.
class Program
{
List<DriveInfo> driveList = DriveInfo.GetDrives().Where(x => x.IsReady).ToList<DriveInfo>(); //Get all the drive info
Server server = new Server(); //Create the server object
ServerDrive serverDrives = new ServerDrive();
public static void Main()
{
Program c = new Program();
c.RealDriveInfo();
c.WriteInToDB();
}
public void RealDriveInfo()
{
//Insert information of one server
server.ServerID = 0; //(PK) ID Auto-assigned by SQL
server.ServerName = string.Concat(System.Environment.MachineName);
//Inserts ServerDrives information.
for (int i = 0; i < driveList.Count; i++)
{
//All Information used in dbo.ServerDrives
serverDrives.DriveLetter = driveList[i].Name;
serverDrives.TotalSpace = driveList[i].TotalSize;
serverDrives.DriveLabel = driveList[i].VolumeLabel;
serverDrives.FreeSpace = driveList[i].TotalFreeSpace;
serverDrives.DriveType = driveList[i].DriveFormat;
server.ServerDrives.Add(serverDrives);
}
}
public void WriteInToDB()
{
//Add the information to an SQL Database using Linq.
DataClasses1DataContext db = new DataClasses1DataContext(#"sqlserver");
db.Servers.InsertOnSubmit(server);
db.SubmitChanges();
What I would like it to use to update the information would be the RealDriveInfo() Method so instead of creating new entries it updates the currently stored information by running the method then inserting the information from the method and if needed will enter a new entry instead of simply entering new entries every time it has newer information.
At the moment it is running the method, gathering the relevant data then entering it in as a new row in both tables.
Any help would be appreciated :)
It's creating a new db entry each time because you are making a new server object each time, then calling InsertOnSubmit() - which inserts (creates) a new record.
I'm not entirely sure what you are trying to do, but a db update would involve selecting an existing record, modifying it, then attaching it back to the data context and calling SubmitChanges().
This article on Updating Entities (Linq toSQL) might help.
The problem is that you are trying to achieve Update functionality with a tool that is designed to provide object-oriented quering. LINQ allows for updating exisitng records, but you have to use it in a proper way to achieve this.
The proper way is to fetch data you want to update from the DB, perform modifications and then flush it back to the DB. So, assuming there are table named Servers in your data context, here's an abstract example:
DataClasses1DataContext db = new DataClasses1DataContext(#"sqlserver");
var servers = db.Servers.Where(srv=>srv.ID>1000); //extracting all servers with ID > 100 using lambda expression
foreach (server in servers){
server.Memory *=2; //let's feed them up with memory
}
db.Servers.SubmitChanges();
Another way to achieve this is to create an entity, than attach it to the DataContext using Table.Attach method, but it's quite a slippery slope, so I wouldn't recommend you taking it unless you have your LINQ skills improved.
For a detailed description, see
SubmitChanges
Lambda Expressions
I understand what is being asked, and I do not have an easy answer.
Example, you have a form of values, several of the values are changed, maybe some calculated. Or the form can contain a new record.
You create a record of the values
myrecord = new MyRecord()
Then fill in myRecord. doing what ever validation/calculations you want before you even touch the database itself.
//GetID either returns an existing ID or it returns a zero if this is a new record.
myrecord.id = GetIDForRecordOrZeroIfANewRecord(uniqueName);
myrecord.value1 = txtValue1.text;
myrecord.value2 = (DateTime)dtDate.value;
and so on through the fields.
You now have a record, if id is zero you can add it as a new record. But if id is an existing record you seem to have no choice with Linq except to have a function that writes each value from myrecord, so you have to have a function that contains something like -
var thisRecord = from n in mydatacontext.MyTable
where n.id == myrecord.id
select n;
thisrecord.value1 = myrecord.value1;
thisrecord.value2 = myrecord.value2;
and so on through all fields.
I do it, but it seems long winded when I already have all of the information ready in myrecord. A simple function of
mydatacontext.MyTable.Update(myrecord);
Would be ideal. Simmilar in fact to what I do with stored SQL functions in other databases, it simplifies the transfer of a record that is an update rather than new.

How to add or remove a many-to-many relationship in Entity Framework?

There's a many-to-many UserFeed table that stands between User and Feed and denotes a twitter-like follow relationship.
It only has two fields, which form a composite key: UserID and FeedID.
I need to write a method that will subscribe or unsubscribe a user from a feed based on a boolean flag.
public void SetSubscriptionFlag (int userId, int storeId, bool subscribe)
{
}
I'm new to Entity Framework so I'm trying to find and follow an "EF-ish" way to accomplish this.
My initial thoughts are:
Instead of working with the middle UserFeed class, I should create a many-to-many Subscriptions property (EDIT: hit limitations here);
After I've done so, I'll need to fetch a User instance by ID, check whether it has given Feed in its Subscriptions and add/delete it depending on the flag and current existence;
Figure out how to avoid racing conflicts when there is a time interval before the check and adding/deleting and user manages to submit two adding or deletion requests;
Optimize my code as to avoid unneccessary SELECTs, if any occur, because all I really want to do is a single SELECT and single INSERT/DELETE.
A relevant code snippet and comment on my points is highly appreciated.
Thanks!
You can use dummy objects - it definitely works for insert and I hope it can be used for delete as well:
Create new relation:
var user = new User() { Id = userId };
context.Users.Attach(user);
var store = new Store() { Id = storeId };
context.Stores.Attach(store);
// Now context trackes both entities as "existing"
// and you can build a new relation
user.Subscriptions.Add(store);
context.SaveChanges();
Remove existing relation:
var user = new User() { Id = userId };
var store = new Store() { Id = storeId };
user.Subscriptions.Add(store);
context.Users.Attach(user);
// Now context trackes both entities as "existing"
// with "existing" relation so you can try to remove it
user.Subscriptions.Remove(store);
context.SaveChanges();

Save a relation with between two entities an N-N association

I've a Entity Framework 4.0, with poco object. the edmx model file is generated from the database.
This datacontext is accessed through WCF service, it's only mean that I receive some objects and I need to attach them to the current datacontext(or reload them with the key correspondance).
Everything seems to work fine, except for one case:
I've a N-N relationship between two table, so I've an association table, without any field other than ID of two tables:
LINQ transform this into the following schema, this seems to be right.
When I retrieve data, there is no problem, data I've inserted myself in the Right_group are correctly transformed into "new object in my collection of Rights/Groups".
But if I try to modify something and save, it doesn't work
public void SaveRights(Group group, List<Rights> rights){
//here, group and rights are objects attached to the database
group.Rights.Clear();
group.Rights.AddRange(rights);
_dataContext.SaveChanges();
}
So my question is: How to save this "relationship" of two objects ?
Thank you!
If you want to avoid loading the objects from the database first you can do it like this(Code taken from one of my aplications so you will have to adapt it):
public void AddAndRemovePersons(int id, int[] toAdd, int[] toDelete)
{
var mailList = new MailList { ID = id, ContactInformations = new List<ContactInformation>() };
this.db.MailLists.Attach(mailList);
foreach (var item in toAdd)
{
var ci = new ContactInformation { ID = item };
this.db.ContactInformations.Attach(ci);
this.db.ObjectStateManager.ChangeRelationshipState(mailList, ci, ml => ml.ContactInformations, System.Data.EntityState.Added);
}
foreach (var item in toDelete)
{
var ci = new ContactInformation { ID = item };
this.db.ContactInformations.Attach(ci);
this.db.ObjectStateManager.ChangeRelationshipState(mailList, ci, ml => ml.ContactInformations, System.Data.EntityState.Deleted);
}
}
I found deleting the relationship as hard as creating it so I left that code in there. One thing about this solution is that both the maillist and the contacts exist prior to this function being run. I attach them to make the state manager track them.
If you are adding new objects that you also want to save you would use the
this.db.MailLists.AddObject(you new item here)
I hope that helps!
Just a thought... how are the keys setup in the Right_Group table? If you use both IDRight and IDGroup together as primary key - this problem might occur. One suggetion is to add a new column (ID) into the Right_Group table, and having this ID as the primary key. Then use foreign keys on the other columns (IDRight, IDGroup) respectivly.

Categories