I have an issue that I am trying to resolve where I have a relationship between two tables. As soon as I save a record to the 1st Table I then want to read that same record back so I can get the primary key and assign it to my next table.
Code I have written is below
public IEnumerable<CombinedPayCode> ImportCombinedPayCodes()
{
var xmlFile = Path.Combine(_xmlPath, "WSACombinedPayCodeRule.xml");
var stream = new FileStream(xmlFile, FileMode.Open, FileAccess.Read);
var xdoc = XDocument.Load(stream);
var combinedPayCodeCollection = xdoc.Descendants("WSACombinedPayCodeRule");
var Collection = new List<CombinedPayCode>();
foreach (var element in combinedPayCodeCollection)
{
var combinedPayCode = new CombinedPayCode
{
Name = (string)element.Attribute("Name"),
AmountType = (string)element.Attribute("AmountType"),
VisibleInReport = (bool)element.Attribute("VisibleInReport"),
MoneyCategory = (bool)element.Attribute("MoneyCategory"),
VisibleToUser = (bool)element.Attribute("VisibleToUser"),
CustomerId = 11,
};
_db.CombinedPayCodes.Add(combinedPayCode);
// I want to obtain the Primary Key of the above record once saved to database
var payCodeNodes = element.Descendants("SimpleValue");
foreach (var selectedPayCode in payCodeNodes)
{
var combinedPayCodeList = new CombinedPayCodeList
{
PayCodeId = selectedPayCode.FirstAttribute.Value,
// I want to replace the next line of code to assign the
// foreign key as per the primary key of the record above
CombinedPayCodeId = 1,
};
_db.CombinedPayCodeLists.Add(combinedPayCodeList);
}
_db.SaveChanges();
Collection.Add(combinedPayCode);
}
return Collection;
}
Is the primary key of CombinedPayCode an identity column? If so, just reference combinedPayCode.ThePrimaryKeyColumn in your code.
If CombinedPayCodeList is a collection of something that hangs off CombinedPayCode you can save yourself some effort and just build the object graph and add the parent object then SubmitChanges(). EF will take care of transferring the primary key into the child entity and wrap everything in a nice neat transaction for you.
UPDATE: Something like this. I may have some of your object/property names wrong.
foreach( var element in combinedPayCodeCollection )
{
// Create the CombinedPayCode object
var combinedPayCode = new CombinedPayCode
{
Name = (string)element.Attribute("Name"),
AmountType = (string)element.Attribute("AmountType"),
VisibleInReport = (bool)element.Attribute("VisibleInReport"),
MoneyCategory = (bool)element.Attribute("MoneyCategory"),
VisibleToUser = (bool)element.Attribute("VisibleToUser"),
CustomerId = 11,
};
var payCodeNodes = element.Descendants("SimpleValue");
foreach( var selectedPayCode in payCodeNodes )
{
var combinedPayCodeList = new CombinedPayCodeList
{
PayCodeId = selectedPayCode.FirstAttribute.Value,
};
// Add each PayCodeNode to the CombinedPayCode
combinedPayCode.CombinedPayCodeLists.Add( combinedPayCodeList );
}
// Add the CombinedPayCode (which includes all the PayCodeNodes) to
// the object context and save the whole shebang.
_db.CombinedPayCodes.Add(combinedPayCode);
_db.SaveChanges();
I'm guess that you are using Entity Framework based off the syntax.
You can do this:
_db.CombinedPayCodes.Add(combinedPayCode);
_db.SaveChanges();
Then combinedPayCode.Id should have it's primary key. This not the best way to do it if you need that ID as a Foreign key on another table. Then you would do:
combinedPayCode.CombinedPayCodeLists.Add(combinedPayCodeList);
_db.CombinedPayCodes.Add(combinedPayCode);
_db.SaveChanges();
In this case you only have to save one time, and it wraps all the db changes in a transaction. If one fails, they all fail.
Hope this helps!
Related
I'm trying to insert a new record from TPOSTransaction and TPOSTransactionDetail
The code below is from an event in my class:
_posTransaction = new TPostransaction();
_posTransactionDetail = new TPostransactionDetail();
_posTransactionDetails = new List<TPostransactionDetail>();
_posTransaction.UserId = _user.Id;
_posTransaction.Total = Convert.ToDecimal(txtOrderTotal.Text);
_posTransaction.ChangeDue = Convert.ToDecimal(txtChangeDue.Text);
_posTransaction.AmountPaid = Convert.ToDecimal(txtAmountPaid.Text);
_posTransaction.TransactionDate = DateTime.Now;
foreach (ListViewItem orders in lvOrders.Items)
{
_posTransactionDetail.ItemId = Convert.ToInt32(orders.SubItems[1].Text);
_posTransactionDetail.Quantity = Convert.ToInt32(orders.SubItems[3].Text);
_posTransactionDetail.Subtotal = Convert.ToDecimal(orders.SubItems[4].Text) * Convert.ToDecimal(orders.SubItems[3].Text);
_posTransactionDetails.Add(_posTransactionDetail);
}
_posTransaction.TPostransactionDetail = _posTransactionDetails;
_pos.Add(_posTransaction); //method from a class
This is where I save the record:
When I try to debug, POSTransaction.TposTransactionDetail have 2 records since it's what I added from the list
//this is my primary concern
var posTransaction = dbContext.TPostransaction.Add(POSTransaction);
dbContext.TPostransactionDetail.AddRange(POSTransaction.TposTransactionDetail);
dbContext.SaveChanges();
This saves the record of TPosTransaction and TPosTransactionDetail, but the detail that is being saved is only one, instead of 2. I tried removing the AddRange part it still saves a single record in my TposTransactionDetail.
on you foreach you need to create new instance of TPostransactionDetail, you are just reusing the instance on the top of your code.
When data from a device goes into the elastic there are duplicates. I like to avoid this duplicates. I'm using a object of IElasticClient, .NET and NEST to put data.
I searched for a method like ElasticClient.SetDocumentId(), but cant find.
_doc doc = (_doc)obj;
HashObject hashObject = new HashObject { DataRecordId = doc.DataRecordId, TimeStamp = doc.Timestamp };
// hashId should be the document ID.
int hashId = hashObject.GetHashCode();
ElasticClient.IndexDocumentAsync(doc);
I would like to update the data set inside the Elastic instead of adding one more same object right now.
Assuming the following set up
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(pool)
.DefaultIndex("example")
.DefaultTypeName("_doc");
var client = new ElasticClient(settings);
public class HashObject
{
public int DataRecordId { get; set; }
public DateTime TimeStamp { get; set; }
}
If you want to set the Id for a document explicitly on the request, you can do so with
Fluent syntax
var indexResponse = client.Index(new HashObject(), i => i.Id("your_id"));
Object initializer syntax
var indexRequest = new IndexRequest<HashObject>(new HashObject(), id: "your_id");
var indexResponse = client.Index(indexRequest);
both result in a request
PUT http://localhost:9200/example/_doc/your_id
{
"dataRecordId": 0,
"timeStamp": "0001-01-01T00:00:00"
}
As Rob pointed out in the question comments, NEST has a convention whereby it can infer the Id from the document itself, by looking for a property on the CLR POCO named Id. If it finds one, it will use that as the Id for the document. This does mean that an Id value ends up being stored in _source (and indexed, but you can disable this in the mappings), but it is useful because the Id value is automatically associated with the document and used when needed.
If HashObject is updated to have an Id value, now we can just do
Fluent syntax
var indexResponse = client.IndexDocument(new HashObject { Id = 1 });
Object initializer syntax
var indexRequest = new IndexRequest<HashObject>(new HashObject { Id = 1});
var indexResponse = client.Index(indexRequest);
which will send the request
PUT http://localhost:9200/example/_doc/1
{
"id": 1,
"dataRecordId": 0,
"timeStamp": "0001-01-01T00:00:00"
}
If your documents do not have an id field in the _source, you'll need to handle the _id values from the hits metadata from each hit yourself. For example
var searchResponse = client.Search<HashObject>(s => s
.MatchAll()
);
foreach (var hit in searchResponse.Hits)
{
var id = hit.Id;
var document = hit.Source;
// do something with them
}
Thank you very much Russ for this detailed and easy to understand description! :-)
The HashObject should be just a helper to get a unique ID from my real _doc object. Now I add a Id property to my _doc class and the rest I will show with my code below. I get now duplicates any more into the Elastic.
public void Create(object obj)
{
_doc doc = (_doc)obj;
string idAsString = doc.DataRecordId.ToString() + doc.Timestamp.ToString();
int hashId = idAsString.GetHashCode();
doc.Id = hashId;
ElasticClient.IndexDocumentAsync(doc);
}
I'm trying to copy ProductStatisticsTemp table data to ProductStatistics table,
var str = from a in db.ProductStatisticsTemp select a;
ProductStatistics ls = new ProductStatistics();
foreach (var val in str.ToList())
{
ls.Product_ID = val.Product_ID;
ls.ProductNameEn = val.ProductNameEn;
ls.ProductNameAr = val.ProductNameAr;
db.ProductStatistics.Add(ls);
db.SaveChanges();
}
first record can insert but once its try to insert 2nd one getting following error
The property 'Product_ID' is part of the object's key information and
cannot be modified.
It's because you have one instance of an object and try to add already added object twice.
You need to create new object of ProductStatistics in the loop.
Also you can save changes just once after the loop to improve performance by trigger DB communication just once:
var str = from a in db.ProductStatisticsTemp select a;
foreach (var val in str.ToList())
{
ProductStatistics ls = new ProductStatistics
{
Product_ID = val.Product_ID,
ProductNameEn = val.ProductNameEn,
ProductNameAr = val.ProductNameAr
};
db.ProductStatistics.Add(ls);
}
db.SaveChanges();
Here is a slightly different method.
var products = db.ProductStatisticsTemp.Select(t => new ProductStatistics
{
Product_ID = t.Product_ID,
ProductNameEn = t.ProductNameEn,
ProductNameAr = t.ProductNameAr
}).ToList()
db.ProductStatistics.AddRange(products);
db.SaveChanges();
IMHO Inspired from #Vadim Martynov
If the Product_ID is your primary key, and your set to increment
the key from database . Do not do this Product_ID = val.Product_ID.
The key should be generated from the database. You will get the id
after save changes is invoked.
try
{
var str = from a in db.ProductStatisticsTemp select a;
//This will improve some performance
db.Configuration.AutoDetectChangesEnabled = false;
foreach (var val in str.ToList())
{
ProductStatistics ls = new ProductStatistics
{
Product_ID = val.Product_ID,
ProductNameEn = val.ProductNameEn,
ProductNameAr = val.ProductNameAr
};
//use AddRange or Add based on your EF Version.
db.ProductStatistics.Add(ls);
}
db.SaveChanges();
}
finally
{
db.Configuration.AutoDetectChangesEnabled = true;
}
If you are using AddRange you could omit db.Configuration.AutoDetectChangesEnabled = false
For more info about DetectChanges available here
AddRange() method only support from EF6 see documentation
db.ProductStatistics.AddRange(products);
What AddRange will do for you is
if AutoDetectChangesEnabled is set to true (which is the default), then DetectChanges will be called once before adding any entities and will not be called again.
This means that in some situations AddRange may perform significantly
better than calling Add multiple times would do.
Note that entities that are already in the context in some other state will have their state set to Added. AddRange is a no-op for entities that are already in the context in the Added state.
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();
}
Hey. Nooby question but new with Entity.
I am trying to create a new user object and some details in some additional tables which have a foreign key of the newly created user id.
I've attempted to do this in one round trip. Do I have to add the user to the database first and then go back, set the userid's on the other objects and add them. Here is the Entity Model and code to elaborate:
using (var context = new ServicesEntities())
{
newUser = new UsersTable();
newUser.Address = user.UserDetails.Address;
newUser.City = user.UserDetails.City;
newUser.Country = user.UserDetails.Country;
newUser.Email = user.Email.EmailString;
newUser.FirstName = user.UserDetails.FirstName;
newUser.LastName = user.UserDetails.LastName;
newUser.State = user.UserDetails.State;
newUser.Zip = user.UserDetails.Zip;
context.UsersTables.AddObject(newUser);
context.SaveChanges();
}
using (var context = new ServicesEntities())
{
var referralDetails = new UserReferrals();
referralDetails.CreatedThruServiceId = 1; // todo don't make this an absolute 1
referralDetails.ReferralEmail = user.ReferralDetails.ReferralEmail;
referralDetails.TwoPlusTwoHandle = user.ReferralDetails.TwoPlusTwoHandle;
referralDetails.UserId = newUser.UserId;
context.UserReferrals.AddObject(referralDetails);
context.SaveChanges(); // THIS WORKS FINE!
}
using (var context = new ServicesEntities())
{
var credentials = new UserCredentials();
credentials.CreatedOn = DateTime.Now;
credentials.EmailValidated = false;
//credentials.EmailValidatedOn = null;
credentials.FailedLoginAttempts = 0;
credentials.IsLockedOut = false;
//credentials.LastValidLogin = null;
credentials.Password = user.Password.PasswordString;
credentials.PermissionId = 1; // todo don't make this an absolute 1 = user
credentials.SuccessfulLoginAttempts = 0;
credentials.UserId = newUser.UserId; ;
context.UserCredentials.AddObject(credentials);
context.SaveChanges(); // THIS ONE CRAPS OUT!
}
When I run this I get the following Exception when I run SaveChanges():
{"A dependent property in a
ReferentialConstraint is mapped to a
store-generated column. Column:
'UserId'."}
Note: Updated this with some slightly different code based on an example in a book.
Note2: I've narrowed down the problem to be in the adding of credentials.
Note3: Fixed this, I accidentally had AUTO-INCREMENT set on my UserCredentials userid. If anyone ares here is working code:
public POCO.User AddNewUserToDb(User user)
{
if (IsDuplicateUser(user.Email.EmailString))
{
throw new DuplicateNameException("This email is already taken.");
}
UsersTable newUser;
using (var context = new ServicesEntities())
{
newUser = new UsersTable();
newUser.Address = user.UserDetails.Address;
newUser.City = user.UserDetails.City;
newUser.Country = user.UserDetails.Country;
newUser.Email = user.Email.EmailString;
newUser.FirstName = user.UserDetails.FirstName;
newUser.LastName = user.UserDetails.LastName;
newUser.State = user.UserDetails.State;
newUser.Zip = user.UserDetails.Zip;
var referralDetails = new UserReferrals();
referralDetails.CreatedThruServiceId = 1; // todo don't make this an absolute 1
referralDetails.ReferralEmail = user.ReferralDetails.ReferralEmail;
referralDetails.TwoPlusTwoHandle = user.ReferralDetails.TwoPlusTwoHandle;
//referralDetails.UserId = newUser.UserId;
var credentials = new UserCredentials();
credentials.CreatedOn = DateTime.Now;
credentials.EmailValidated = false;
//credentials.EmailValidatedOn = null;
credentials.FailedLoginAttempts = 0;
credentials.IsLockedOut = false;
//credentials.LastValidLogin = null;
credentials.Password = user.Password.PasswordString;
credentials.PermissionId = 1; // todo don't make this an absolute 1 = user
credentials.SuccessfulLoginAttempts = 0;
//credentials.UserId = newUser.UserId; ;
newUser.Credentials = credentials;
newUser.ReferralDetails = referralDetails;
context.UsersTables.AddObject(newUser);
context.SaveChanges();
}
user.UserId = newUser.UserId;
return user;
Try to add related records directly to UserTable record:
newUser.Credentials.Add(credentials);
newUser.ReferralDetails.Add(referralDetails);
Do not set any Id. It will be set during saving automatically.
Edit: Btw. make sure that UserId column in UserCredentials table is not set as auto generated in database.
Take a look at these links:
Using the entity framework to add existing entities to a collection on a newly created entity.
How to create foreign key relationships with the Entity Framework?
Entity Framework - insert new object with a collection of existing objects
The key links for EF4:
Fortunately, in EF4 we can directly
update the relationship due to the
Foreign Key Association which allows
us to keep the foreign key property
inside the entity classes. For
detail, please see
http://blogs.msdn.com/adonet/archive/2009/11/06/foreign-key-relationships-in-the-entity-framework.aspx.
Besides, we have another great feature
Self Tracing Entities, which makes the
n-tier pattern much easier in EF,
http://blogs.msdn.com/adonet/archive/2009/11/15/updated-feature-ctp-walkthrough-self-tracking-entities-for-the-entity-framework.aspx.
EF4 will allow you to include the foreign key values as scalar properties of your entities. Make sure the "Include foreign keys" checkbox is checked when you create your EDM.
Looking at your model, it looks like you've already done that. Just set the foreign key values explicitly, no need to do a round trip.