I have a .Net Core project running and I am utilizing 2 different databases (A MySQL db and a PostGreSQL db). I have them set up and they are both implemented in my current controller - TripController.cs
TripController.cs
public IActionResult Index()
{
var viewModels = new List<TripViewModel>();
var Dids = _tripContext.Dids.ToList();
foreach (var Did in Dids)
{
IQueryAble<Tripmetadata> trips = _tripContext.Tripmetadata.Where(t => t.Did == Did.Did);
var tripsCount = trips.Count()
//--------------------- I believe error is here ---------------------
var alias = _context.Devices.Where(d => (long.Parse(d.Did)) == Did.Did).Select(d => d.Alias).ToString();
// ------------------------------------------------------------------
var viewModel = new TripViewModel
{
TripCount = tripsCount,
didElement = Did,
Alias = alias
};
viewModels.Add(viewModel)
}
return View(viewModels);
}
_context is the MySQL db and _tripContext is the PostGreSQL db.
Both database have a field called Did which I need to use. For the PostGreSQL db I need to use it to get the amount of trips (tripCount) for a given Did. However for the MySQL db I need to use the Did to get the alias for a device.
When I try to use the above code from the TripController to get the alias for a Did I get a weird value when displaying it in my view:
Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[System.String]
as seen here:
I use a viewModel called TripViewModel which I pass to my View:
TripViewModel.cs
public class TripViewModel
{
public int TripCount {get;set;}
public long DidElement {get;set;}
public string Alias {get;set;}
}
How do I get it write the right Alias in my view?
I have tried numerous methods that doesn't yield the result I need.
I managed to find the solution.
My problem was not adding a .Single() at the end of my EF query.
I also found that this post is a duplicate. However I didn't know what my problem was until a stumpled upon this:
Is it possible to update objects with Entity Framework, without grabbing them first?
Example: Here, I have a function that provides a Primary Key to locate the objects, pulls them, then updates them. I would like to eliminate having to pull the objects first, and simply run an UPDATE query. Removing the need for the SELECT query being generated.
public async Task<int> UpdateChecks(long? acctId, string payorname, string checkaccountnumber, string checkroutingnumber, string checkaccounttype)
{
using (var max = new Max(_max.ConnectionString))
{
var payments = await
max.payments.Where(
w =>
w.maindatabaseid == acctId && (w.paymentstatus == "PENDING" || w.paymentstatus == "HOLD")).ToListAsync();
payments.AsParallel().ForAll(payment =>
{
payment.payorname = payorname;
payment.checkaccountnumber = checkaccountnumber;
payment.checkroutingnumber = checkroutingnumber;
payment.checkaccounttype = checkaccounttype;
payment.paymentmethod = "CHECK";
payment.paymentstatus = "HOLD";
});
await max.SaveChangesAsync();
return payments.Count;
}
}
You can use the Attach() command to attach an entity you already know exists and then call SaveChanges() will will call the appropriate update method. Here is some sample code from the MSDN article on the topic:
on the subject:
var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" };
using (var context = new BloggingContext())
{
context.Entry(existingBlog).State = EntityState.Unchanged;
// Do some more work...
context.SaveChanges();
}
Note that this is general EF logic, not related to any specific database implementation.
EF6.
I have a project using EF6 Database first.
My database table had a unique key on 6 columns. All was working well in the project. I then realized the unique key was incorrect and I didn't need it so I deleted it from the database.
I have a Product which contains an ICollection<ProdPrice> ProdPrices.
Now, when I use Product product = db.Products.Find(id); I get a product but the ProdPrices collection only contains 6 items when it should contain 12. (The first 6 items are in the list).
If I delete the first item from the db and run the code again, 6 items are returned - items 2 - 7.
If I change one of the values in a column that was in the unique key that item will come though in the code.
So I'm thinking EF is somehow remembering the unique key and only returning the first items that do not conflict with the unique key.
I tried to "update model from database" in the edml file - didn't resolve the issue. So I deleted the ProdPrice table from the edml and the "update model from database" - didn't work.
So my question is - Am I correct is saying EF is remembering the deleted unique key? If yes, how do I get it to forget about it? If I am incorrect, then can you explain what is actually happening?
EDIT: SQL generated by the call to the database - standard select statement which returns all 12 records when run in SSMS (columns removed for ease of reading) :
SELECT
[Extent1].[ProdPriceID] AS [ProdPriceID],
[Extent1].[ProdID] AS [ProdID],
[Extent1].[PermanentlyDelete] AS [PermanentlyDelete],
[Extent1].[DateCreated] AS [DateCreated]
FROM [dbo].[ProdPrice] AS [Extent1]
WHERE [Extent1].[ProdID] = 67577
Here are the results so you can see I get 12 records.
Code Result:
So I decided to just request the prices for the product - I get 12 results, this is really confusing me:
EDIT:
I thought I had resolved it...
To resolve (and test further) I decided to recreate the ProdPrice table in the db - I did this by generating the script using MSSSMS and including the data so I had an exact copy of the table, calling it ProdPrice2.
I had both ProdPrice and ProdPrice2 Entities in my system (This confirmed I'm still connected to the correct database). ProdPrice still only returned records that did not conflict with the original Unique Index. ProdPrice2 - returned all records!
Whoop, thought that was it - I then removed ProdPrice from my system, leaving ProdPrice2 - I ran the system, ProdPrice2 now only has 6 records, not the 12 I had previously!!!
I added ProdPrice back in. ProdPrice2 still has 6 records, ProdPrice now has all expected records.
I'M STUMPED!!!! This is really stopping my development! I can't continue until this is resolved!
After wrestling with this for a couple of days and some excellent help and suggestions from #AlbertoMonteiro I couldn't get the behaviour I expected.
After continuing to try various things, and debugging, I eventually hit some code I forgot I had in there...!
So the answer to the original question
Does Entity Framework Database First know about unique keys set in the database?
is, no Entity Framework Database First DOESN'T know about unique keys set in the database.
EF does however, use any overridden Equals() functions when creating lists of entities. In my code I had overridden the Equals functions to mimic the unique keys in the database meaning when I added to lists of Prices I could match "duplicates" and merge them together. This code resulted in my Prices list not being fully populated now that I had changed the way I wanted the system to work.
The answer is that I'm an idiot and I should have remembered the code I had written. I will leave this question here as it may stop someone losing a couple of days development in the future.
Entity Framework in this question, doesn't worry about your deleted Unique Key, also this Unique Key doesn't filter the data when you select something, the unique key prevents that you can not add another row with the same unique key.
So again, this unique key isn't a trouble, and EF doesn't care about it.
But you can debug the SQL generated from EF and check why you are getting only 6 items from property ProdPrices.
We can use the Database.Log property from context, to analyze the SQL generated.
You said that you have this code line Product product = db.Products.Find(id);
Lets modify it a little bit to analyze the sql.
//Add breakpoint in this line
Product product = db.Products.Find(id);
db.Database.Log = sql => System.Diagnostics.Debug.WriteLine(sql);
var prodPrices = product.ProdPrices.ToList();
//Next line
Add a breackpoint in first line(that find the product)
Press F10(Step over) 3 times
Breakpoint must be now in next line of code after the creation of prodPrices
Open the Output window
The SQL generated from ProdPrice, should be there
Analyze the SQL query, execute in SSMS and check the result.
I tried this in my machine, but it works fine:
public class Program
{
public static void Main(string[] args)
{
using (var ctx = new MyClass())
{
if (!ctx.Products.Any())
{
var product = new Product
{
ProdPrices = new List<ProdPrice>
{
new ProdPrice {PermanentlyDelete = true, DateCreated = new DateTime(2015,12,29,18,28,27)},
new ProdPrice {PermanentlyDelete = true, DateCreated = new DateTime(2015,12,29,18,28,28)},
new ProdPrice {PermanentlyDelete = true, DateCreated = new DateTime(2015,12,29,18,28,28)},
new ProdPrice {PermanentlyDelete = true, DateCreated = new DateTime(2015,12,29,18,28,29)},
new ProdPrice {PermanentlyDelete = true, DateCreated = new DateTime(2015,12,29,18,28,29)},
new ProdPrice {PermanentlyDelete = true, DateCreated = new DateTime(2015,12,30,19,08,38)},
new ProdPrice {PermanentlyDelete = false, DateCreated = new DateTime(2015,12,31,10,18,06)},
new ProdPrice {PermanentlyDelete = false, DateCreated = new DateTime(2015,12,31,10,18,06)},
new ProdPrice {PermanentlyDelete = false, DateCreated = new DateTime(2015,12,31,10,18,07)},
new ProdPrice {PermanentlyDelete = false, DateCreated = new DateTime(2015,12,31,10,18,07)},
new ProdPrice {PermanentlyDelete = false, DateCreated = new DateTime(2015,12,31,10,18,08)},
new ProdPrice {PermanentlyDelete = false, DateCreated = new DateTime(2015,12,31,10,18,08)},
}
};
ctx.Products.Add(product);
ctx.SaveChanges();
Console.WriteLine("Saved");
}
}
using (var ctx = new MyClass())
{
var product = ctx.Products.Find(1);
var count = product.ProdPrices.Count;
Console.WriteLine(count);
}
}
}
public class MyClass : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<ProdPrice> ProdPrices { get; set; }
}
public class Product
{
[Key]
public long ProdID { get; set; }
public virtual ICollection<ProdPrice> ProdPrices { get; set; }
}
public class ProdPrice
{
public long ProdPriceID { get; set; }
public bool PermanentlyDelete { get; set; }
public DateTime DateCreated { get; set; }
}
It seems you are creating the dbcontext only once, and that too is static.
When ever you are making a fetch/write call to db create a new context, that should do.
Again,
Create a base class which contains DbContext and inherit that class every time you need to talk to the db.
Something like this
public class TheDBEntities
{
private TheDBEntities _context = new TheDBEntities();
protected TheDBEntities Context { get; private set; }
protected TheDataContext()
{
Context = _context;
}
protected void SaveChanges()
{
Context.SaveChanges();
}
protected void SaveChangesAsync()
{
Context.SaveChangesAsync();
}
}
public class DBBusinessLayer: TheDBEntities
{
public DBBusinessLayer() { }
public void GetData(int id)
{
// use it here Context.<yourentities>
}
}
So, in a desperate attempt to wrangle EntityFramework into being usable. I am here..
private MyEntity Update(MyEntity orig)
{
//need a fresh copy so we can attach without adding timestamps
//to every table....
MyEntity ent;
using (var db = new DataContext())
{
ent = db.MyEntities.Single(x => x.Id == orig.Id);
}
//fill a new one with the values of the one we want to save
var cpy = new Payment()
{
//pk
ID = orig.ID,
//foerign key
MethodId = orig.MethodId,
//other fields
Information = orig.Information,
Amount = orig.Amount,
Approved = orig.Approved,
AwardedPoints = orig.AwardedPoints,
DateReceived = orig.DateReceived
};
//attach it
_ctx.MyEntities.Attach(cpy, ent);
//submit the changes
_ctx.SubmitChanges();
}
_ctx is an instance variable for the repository this method is in.
The problem is that when I call SubmitChanges, the value of MethodId in the newly attached copy is sent to the server as 0, when it is in fact not zero if I print it out after the attach but before the submit. I am almost certain that is related to the fact that the field is a foreign key, but I still do not see why Linq would arbitrarily set it to zero when it has a valid value that meets the requirements of the constraint on the foreign key.
What am I missing here?
You should probably set Method = orig.Method, but I can't see your dbml, of course.
I think you need to attach the foreign key reference
var cpy = new Payment()
{
//pk
ID = orig.ID,
//other fields
Information = orig.Information,
Amount = orig.Amount,
Approved = orig.Approved,
AwardedPoints = orig.AwardedPoints,
DateReceived = orig.DateReceived
};
//create stub entity for the Method and Add it.
var method = new Method{MethodId=orig.MethodId)
_ctx.AttachTo("Methods", method);
cpy.Methods.Add(method);
//attach it
_ctx.MyEntities.Attach(cpy, o);
//submit the changes
_ctx.SubmitChanges();
I have two table like this:
**Complaint**
-Id
-CreatedBy
-CreatedDate
....
**Solution**
-Id
-ComplaintId
Sometimes, a complaint has an instant solution, which means, when it is created, a solution is also created. The Database is Oracle, and to insert new record into database, I set the StoredGeneratePattern to Identity and use trigger to insert a sequence's value.
here my code:
using (var context = new Entities())
{
var complaint = new Complaint
{
Title = TitleTextBox.Text.Trim(),
CreatedBy = CurrentUser.UserID,
Description = DescriptionTextBox.Text.Trim(),
ServiceId = Convert.ToDecimal(ddlService2.Value),
Contact = ContactTextBox.Text.Trim(),
CreatedDate = DateTime.Now,
Customer = txtUserName.Text.Trim(),
ResellerId = CurrentUser.ResellerID,
Status = ComplaintStatus.GetStatusCode("New complaint")
};
if (CompletedCheckBox.Checked)
{
complaint.Status = ComplaintStatus.GetStatusCode("Completed");
var solution = new Solution
{
CreatedBy = CurrentUser.UserID,
CreatedDate = DateTime.Now,
SolutionDesc = DescriptionTextBox.Text,
ComplaintId = complaint.Id
};
context.Solutions.AddObject(solution);
}
context.Complaints.AddObject(complaint);
if(context.SaveChanges() > 0)
{
ResetFrom();
return true;
}
}
the problem is, I can't get the id of newly created complaint to set the field in the solution. How can I do that?
Thank you.
Could you not perform the first operation call SaveChanges() and then query your complaint object which should now have a complaintID.
Assuming you are using a trigger/sequence with Oracle, you will need to do a get after you save changes to get an object with the Id populated. If you are not using a trigger, you can set the Id manually on the new object by getting the next value from the sequence.
If you add the complaint and SaveChanges() before you create the solution the complaint object will have the Identity value, then after creating the solution add it to the context and call SaveChanges() a second time.
context.Complaints.AddObject(complaint);
if (CompletedCheckBox.Checked)
{
complaint.Status = ComplaintStatus.GetStatusCode("Completed");
context.SaveChanges();
var solution = new Solution
{
CreatedBy = CurrentUser.UserID,
CreatedDate = DateTime.Now,
SolutionDesc = DescriptionTextBox.Text,
ComplaintId = complaint.Id
};
context.Solutions.AddObject(solution);
}
if(context.SaveChanges() > 0)
{
ResetFrom();
return true;
}
Also if you were to add a Foreign Key Relationship Between the Solution and the Complaint, you would no set the ComplaintId, you would just set solution.Complaint = complaint and the Ids would be set correctly during the save.
The answer is actually easy. In this case I do not believe you need the ID at all (at least not just to add this relationship), but in case you do, do this:
Make sure you have the ID on the Complaint entity to refresh on Insert (We use DevArt, but I forget the exact setting name here, but if you select the ID on the entity you should see an UpdateMode or something like that that needs to be set to UpdateOnInsert i think), then
To just insert this relationship do this:
using (var context = new MyContext())
{
var complaint = new Complaint {...};
context.Complaints.AddObject(complaint);
var solution = new Solution {..., Complaint = complaint};
context.Solutions.AddObject(solution);
context.SaveChanges();
}
You will not want to do SaveChanges twice as that requires a separate transactionscope. This way you don't need it.
You can add the Complaint to the Solutions "Complaint" navigation property.
So create your solution object like you are doing then do the following:
Soltion.Complaint = newCreatedComplaintObject;