Silverlight 4 wcf ria Saving multiple records - entity-framework

Ok, I'm pretty sure its just a matter of learning... but I have a very normalized db i'm working with so when I save to my product tbl I also have a productDollar tble and so on...
my question is in silverlight everything is async so How do I save a product get back its new id and use that as the productDollar.productID fk
so far with my other saves i just use the submitOperation in the callback of the submitchanges
and in there i check for iscompleted and do the next save and so on... and chain them together like that.
but I have 500 products I need to save (all at once)
so doing a foreach around my product object won't work because of the wonderful async
So what am I missing??? any help or pointers would be GREATLY appreciated

WCF RIA Services had this situation in mind when it was created. You can easily do it all in one SubmitChanges request and in one database transaction (depending on your DB and/or ORM). However, if you provide some more information about your objects (POCO, EF, etc.), you'll get a better answer.
That said, I'll take a wild guess at your objects as defined on the server.
public class Product
{
[Key]
public int? ProductID { get; set; }
// ... more properties ...
[Association("Product-ProductDollars", "ProductID", "ProductID", IsForeignKey = false)]
[Include]
[Composition]
public ICollection<ProductDollar> ProductDollars { get; set; }
}
public class ProductDollar
{
[Key]
public int? ProductDollarID { get; set; }
public int? ProductID { get; set; }
// ... more properties ...
[Association("Product-ProductDollars", "ProductID", "ProductID", IsForeignKey = true)]
[Include]
public Product Product { get; set; }
}
And your DomainService looks something like
public class ProductDomainService : DomainService
{
public IQueryable<Product> GetProducts()
{
// Get data from the DB
}
public void InsertProduct(Product product)
{
// Insert the Product into the database
// Depending on how your objects get in the DB, the ProductID will be set
// and later returned to the client
}
public void InsertProductDollar(ProductDollar productDollar)
{
// Insert the ProductDollar in the DB
}
// Leaving out the Update and Delete methods
}
Now, on your client, you'll have code that creates and adds these entities.
var context = new ProductDomainContext();
var product = new Product();
context.Products.Add(product);
product.ProductDollars.Add(new ProductDollar());
product.ProductDollars.Add(new ProductDollar());
context.SubmitChanges();
This results in one request sent to the DomainService. However, WCF RIA splits this ChangeSet containing the 3 inserts into 3 calls to your DomainService methods:
InsertProduct(Product product)
InsertProductDollar(ProductDollar productDollar)
InsertProductDollar(ProductDollar productDollar)
If your DomainService performs all inserts in one transaction, the ProductID can be correctly managed by your ORM.

Related

How to retrieve new ID in EF Core using UoW pattern

I'm having trouble retrieving the ID of newly added object in EF Core using the UoW pattern. I have this service:
public class OrderService : IOrderService
{
private IUnitOfWork _uow;
private IOrderRepository _orderRepository;
private IPaymentRepository _paymentRepository;
public OrderService(IUnitOfWork uow,
IOrderRepository orderRepository,
IPaymentRepository paymentRepository)
{
_uow = uow;
_orderRepository = orderRepository;
_paymentRepository = paymentRepository;
}
public int CreateOrder(Logic.Order order)
{
var id = _orderRepository.CreateOrder(order);
var payment = new Data.Payment();
payment.OrderId = id; // at this point, this is -2147353458 something
_paymentRepository.CreatePayment(payment);
// committed at this point but I can't get id unless I re-query
_uow.Commit();
// this is still -2147353458
return id;
}
}
So CreateOrder just adds a new order and then the newly generated ID is returned and used by the Payment object in CreatePayment. The problem with this since after adding, it is not committed yet so EF Core generates a temp id (something like -2147483324) so this is what I get. I then pass this to payment but this part is ok since I think EF is tracking it. The problem is what I return to the UI.
The service is called by the UI and after comitting, I can't get the ID. That's been my problem for hours now.
I've recently came across the same problem as well. Am here just to share my solution for reference.
Rather than to committing the transaction for the Id, you could try utilizing EF relationships.
Ex: the payment and order Model
public class Order
{
public int Id{ get; set; }
public Payment Payment { get; set; }
}
public class Payment
{
public int Id{ get; set; }
public int OrderId{ get; set; }
[ForeignKey("OrderId")]
public Order Order { get; set; }
}
Then in your transaction, you could simply assign the order to payment, EF will automatically insert the created Order Id to payment upon committing the transaction :
public int CreateOrder(Logic.Order order)
{
_orderRepository.CreateOrder(order);
var payment = new Data.Payment();
payment.Order = order;
_paymentRepository.CreatePayment(payment);
_uow.Commit();
return order.id;
}
You need to create an abstract method just like that "void Commit(EntityBase entity)" in your Uow, inside the method call your saveChanges this way you ensure that memory address is the same, so outside of the method you are able to access the property Id, be careful if you're using some Mapper because this may change you Memory Address. Make sure that your are using mapper only after Call UoW.Commit!
public class EntityBase
{
public int Id {get;set}
}
public class AnyOtherEntity : EntityBase
{
}
public void Commit(EntityBase entity)
{
yourContext.SaveChanges(entity);
}
Uow.Commit(AnyOtherEntity);
AnyOtherEntity.Id;
There is no option except commiting the transaction if you want to retrieve the generated unique Id immediately. Also,
// this is still -2147353458
return id;
You can't expect to be changed primitive type after commit. You should get it by order.Id, because after the transaction committed the EF will update entity because EF is tracking the entity.
public int CreateOrder(Logic.Order order)
{
_orderRepository.CreateOrder(order);
// commit the transaction
_uow.Commit();
var payment = new Data.Payment();
// Get the id of inserted row
payment.OrderId = order.Id;
_paymentRepository.CreatePayment(payment);
_uow.Commit();
return order.Id;
}

EF6:How to include subproperty with Select so that single instance is created. Avoid "same primary key" error

I'm trying to fetch (in disconnected way) an entity with its all related entities and then trying to update the entity. But I'm getting the following error:
Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value.
public class Person
{
public int PersonId { get; set; }
public string Personname { get; set }
public ICollection Addresses { get; set; }
}
public class Address
{
public int AddressId { get; set; }
public int PersonId { get; set; }
public string Line1 { get; set; }
public string City { get; set; }
public string State { get; set; }
public Person Person { get; set; }
public ICollection<Feature> Features { get; set; }
}
// Many to Many: Represented in database as AddressFeature (e.g Air Conditioning, Central Heating; User could select multiple features of a single address)
public class Feature
{
public int FeatureId { get; set; }
public string Featurename { get; set; }
public ICollection<Address> Addresses { get; set; } // Many-To-Many with Addresses
}
public Person GetCandidate(int id)
{
using (MyDbContext dbContext = new MyDbContext())
{
var person = dbContext.People.AsNoTracking().Where(x => x.PersonId == id);
person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
return person.FirstOrDefault();
}
}
public void UpdateCandidate(Person newPerson)
{
Person existingPerson = GetPerson(person.Id); // Loading the existing candidate from database with ASNOTRACKING
dbContext.People.Attach(existingPerson); // This line is giving error
.....
.....
.....
}
Error:
Additional information: Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value.
It seems like (I may be wrong) GetCandidate is assigning every Feature within Person.Addresses a new instance. So, how could I modify the GetCandidate to make sure that the same instance (for same values) is bing assisgned to Person.Addresses --> Features.
Kindly suggest.
It seems like (I may be wrong) GetCandidate is assigning every Feature within Person.Addresses a new instance. So, how could I modify the GetCandidate to make sure that the same instance (for same values) is bing assisgned to Person.Addresses --> Features.
Since you are using a short lived DbContext for retrieving the data, all you need is to remove AsNoTracking(), thus allowing EF to use the context cache and consolidate the Feature entities. EF tracking serves different purposes. One is to allow consolidating the entity instances with the same PK which you are interested in this case, and the second is to detect the modifications in case you modify the entities and call SaveChanges(), which apparently you are not interested when using the context simply to retrieve the data. When you disable the tracking for a query, EF cannot use the cache, thus generates separate object instances.
What you really not want is to let EF create proxies which hold reference to the context used to obtain them and will cause issues when trying to attach to another context. I don't see virtual navigation properties in your models, so most likely EF will not create proxies, but in order to be absolutely sure, I would turn ProxyCreationEnabled off:
public Person GetCandidate(int id)
{
using (MyDbContext dbContext = new MyDbContext())
{
dbContext.Configuration.ProxyCreationEnabled = false;
var person = dbContext.People.Where(x => x.PersonId == id);
person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
return person.FirstOrDefault();
}
}

EF many-to-many relationship and data duplication

I have a trouble with EF (6.1.3)
I have created next classes (with many-to-many relationship):
public class Record
{
[Key]
public int RecordId { get; set; }
[Required]
public string Text { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
[Key]
public int TagId { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Record> Records{ get; set; }
}
And method:
void AddTags()
{
Record[] records;
Tag[] tags;
using (var context = new AppDbContext())
{
records = context.Records.ToArray();
}//remove line to fix
tags = Enumerable.Range(0, 5).Select(x => new Tag()
{
Name = string.Format("Tag_{0}", x),
Records= records.Skip(x * 5).Take(5).ToArray()
}).ToArray();
using (var context = new AppDbContext()){ //remove line to fix
context.Tags.AddRange(tags);
context.SaveChanges();
}
}
If I use two contexts, the records (which were added to created tags) will be duplicated. If I remove marked rows - problem disappears.
Is there any way to fix this problem without using the same context?
If you can, better reload entities or not detach them at all. Using multiple context instances in application is overall making things much more complicated.
The problem for you comes from the Entity Framework entity change tracker. When you load entitites from your DbContext and dispose that context, entities get detached from entity change tracker, and Entity Framework has no knowledge of any changes made to it.
After you reference detached entity by an attached entity, it (detached entity) immediately gets into entity change tracker, and it has no idea that this entity was loaded before. To give Entity Framework an idea that this detached entity comes from the database, you have to reattach it:
foreach (var record in records) {
dbContext.Entry(record).State = EntityState.Unchanged;
}
This way you will be able to use records to reference in other objects, but if you have any changes made to these records, then all these changes will go away. To make changes apply to database you have to change state to Added:
dbContext.Entry(record).State = EntityState.Modified;
Entity Framework uses your mappings to determine row in database to apply changes to, specifically using your Primary Key settings.
A couple examples:
public class Bird
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
}
public class Tree
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class BirdOnATree
{
[Column(Order = 0), Key, ForeignKey("Bird")]
public int BirdId { get; set; }
public Bird Bird { get; set; }
[Column(Order = 1), Key, ForeignKey("Tree")]
public int TreeId { get; set; }
public Tree Tree { get; set; }
public DateTime SittingSessionStartedAt { get; set; }
}
Here's a small entity structure so that you could see how it works. You can see that Bird and Tree have simple Key - Id. BirdOnATree is a many-to-many table for Bird-Tree pair with additional column SittingSessionStartedAt.
Here's the code for multiple contexts:
Bird bird;
using (var context = new TestDbContext())
{
bird = context.Birds.First();
}
using (var context = new TestDbContext())
{
var tree = context.Trees.First();
var newBirdOnAtree = context.BirdsOnTrees.Create();
newBirdOnAtree.Bird = bird;
newBirdOnAtree.Tree = tree;
newBirdOnAtree.SittingSessionStartedAt = DateTime.UtcNow;
context.BirdsOnTrees.Add(newBirdOnAtree);
context.SaveChanges();
}
In this case, bird was detached from the DB and not attached again. Entity Framework will account this entity as a new entity, which never existed in DB, even though Id property is set to point to existing row to database. To change this you just add this line to second DbContext right in the beginning:
context.Entry(bird).State = EntityState.Unchanged;
If this code is executed, it will not create new Bird entity in DB, but use existing instead.
Second example: instead of getting bird from the database, we create it by ourselves:
bird = new Bird
{
Id = 1,
Name = "Nightingale",
Color = "Gray"
}; // these data are different in DB
When executed, this code will also not create another bird entity, will make a reference to bird with Id = 1 in BirdOnATree table, and will not update bird entity with Id = 1. In fact you can put any data here, just use correct Id.
If we change our code here to make this detached entity update existing row in DB:
context.Entry(bird).State = EntityState.Modified;
This way, correct data will be inserted to table BirdOnATree, but also row with Id = 1 will be updated in table Bird to fit the data you provided in the application.
You can check this article about object state tracking:
https://msdn.microsoft.com/en-US/library/dd456848(v=vs.100).aspx
Overall, if you can avoid this, don't use object state tracking and related code. It might come to unwanted changes that are hard to find source for - fields are updated for entity when you don't expect them to, or are not updated when you expect it.

Entity Framework: relate identity 2 user to product model

I'm using Web API 2 and Entity Framework 6 and Identity 2
I have product model which relates to an ApplicationUser model, where I create Product, I get an error:
Additional information: An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
My model:
public class Product {
public int id { get; set; }
public string url { get; set; }
public string name {get;set}
public ApplicationUser user { get; set; }
}
My create code:
public IHttpActionResult PostProduct(Product product) {
ApplicationUserManager userManager = new ApplicationUserManager(new UserStore<ApplicationUser>(new ApplicationDbContext()));
product.user = userManager.FindById(User.Identity.GetUserId());
if (!ModelState.IsValid) {
return BadRequest(ModelState);
}
db.Products.Add(product);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = product.id }, product);
}
What dbcontext does "db" refer to?
I would guess that you probably have more than one dbcontext and you are trying to retrieve data from one and persist it on another.
Maybe you can change the code to create just one dbcontext per http request and reuse that context during your http post request.
Maybe change:
ApplicationUserManager userManager = new ApplicationUserManager(new UserStore<ApplicationUser>(new ApplicationDbContext()));
to:
ApplicationUserManager userManager = new ApplicationUserManager(new UserStore<ApplicationUser>(db));
This will let you save your product but you should avoid sending back an EF object, or at least switch off lazy loading.
To clarify a bit, don't send product back in the return as it contains properties which are EF proxies for lazy loading. This will cause at best, a lot more data than you want going back when you serialise the product object and at worst - the error you describe in the comments.

WCF with Entity Framework Code First

I want to use EF behind my WCF service to fetch data and display it to the client. I need the following suggestions:
Do I need to have the same interface for all the views (e.g. students, teachers etc.) or do I need to have a different interface and service for every table (or view)
Do I need to generate the database calls within my service (.svc) or some other architecture is preferred?
public Student[] GetAllStudents()
{
//database generation code here
}
How can I use EF code-first approach to generate database. I know that for an MVC app, you need to set the initializer in Global.asax or in web.config but I am not sure how it's called in this case. My model looks like this:
[DataContract]
public class Student
{
[DataMember]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[DataMember]
public string Type { get; set; }
[DataMember]
public string Subject { get; set; }
[DataMember]
public string Description { get; set; }
}
What you really should do is break up your system in to more separate layers. Instead of having a WCF call that directly queries the database, create a "buisness logic" layer that translates the information that the WCF call provides you to what the EF call needs to know. This is called a N-Tier application
public class SchoolAPI : ISchoolAPI
{
private DataAccessLayer _dal = new DataAccessLayer();
public Student[] GetAllStudents()
{
return _dal.GetStudents(null, null);
}
public Student[] GetAllScienceStudents()
{
return _dal.GetStudents(null, DataAccessLayer.ScienceStudentType);
}
}
private class DataAccessLayer
{
public static readonly ScienceStudentType = //...
public Student[] GetStudents(string subject, string type)
{
using(var ctx = new SchoolContext())
{
IQueryable<Student> studentQuery = ctx.Students;
if(subject != null)
studentQuery = studentQuery.Where(s=>s.Subject == subject);
if(type != null)
studentQuery = studentQuery.Where(s=>s.Type == type);
return studentQuery.ToArray();
}
}
}
The caller of the WCF call does not need to know what the string ScienceStudentType is, all it cares about is that it gets the science students. By seperating the business logic from the database call the caller of your service no longer needs to know.
For EF it will initialize on the first time the framework goes out to "touch" the database and detects that it is not there if it is set up to do so. This is done in the constructor of SchoolContext but is getting a little too broad for this answer. I recommend finding a tutorial on EF and get it working in a simple test enviorment without WCF (maybe a simple console app that just calls GetStudents() then move in in to a WCF environment.