EF Code first problem. It creates the new objects - c#

I am a beginner in Entity Framework Code First. So I am having troubles in working with migrations. So this is an issue:
I have following models:
Defect - table of defects(general)
User - table of users
Order - table or orders
DefectEntry - defects per order(entered by a user).
DefectEntry has:
public virtual Defect defect { get; set; }
public virtual User user { get; set; }
public virtual Order order { get; set; }
All the other models contain:
public virtual ICollection<DefectEntry> Entries { get; set; }
When I am trying to add a defect per order(order & user known) the EF is creating a new order and user objects with compeletely another IDs(Because ID is unique per user and treated as Primary_key).
private Order order;
private User user;
public AddDefect(ref Order order, ref User user) //this is a name of UserControl
{
InitializeComponent();
this.order = order;
this.user = user;
loadDefects();
}
private void loadDefects()
{
using (MyBridgeContext context = new MyBridgeContext())
{
var defects = context.Defects;
foreach (var obj in defects)
{
defectList.Items.Add(obj);
}
}
}
private void addButton_Click(object sender, RoutedEventArgs e)
{
if (doubleCheck.IsChecked == true)
{
try
{
using (MyBridgeContext context = new MyBridgeContext())
{
DefectEntry entry = new DefectEntry();
foreach (Defect def in defectList.Items)
{
if(defectList.SelectedItem == def)
{
entry.defect = def;
}
}
entry.user = user;
entry.order = order;
entry.dt = DateTime.Now;
context.Entries.Add(entry);
context.SaveChanges();
this.Content = new MainMonitoring(ref order, ref user);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
When I push an addButton I have these kind of duplicates:
User table look Defects table look Order table look Defect Entry table look

It does this because your user and order entities are disconnected from EF context. You have to reattach them to the context before using them in DefectEntry object. So you have to do something like this right after using (MyBridgeContext context=new MyBridgeContext()) { statement inside the addButton_Click method:
context.Users.Attach(user);
context.Orders.Attach(order);

Related

How to limit linked entities in Entity Framework Core

When using Entity Framework Core, we've got two linked entities, as a simplified example a course and a booking. A course has a list of bookings. The course also has a property for the number of allowed bookings (This can be different on each course). When inserting a record into the bookings table for a particular course we want to check that the count of bookings is less than the Allowed Bookings. What would be the best way to do this avoiding concurrency conflicts? Multiple people will be creating bookings at the same time.
I've tried using EF to run the select of the count before inserting but if two are inserting at the same time this can cause the bookings to exceed the limit.
I've thought about using a Date Modified column on the Course table and using that as a concurrency check and thereby updating that every time we add a booking but that would cause a concurrency error when the count may have gone from 4 to 5 and is still under the limit of 10, so should go through without any issue. It would also mean we're updating an extra table when really all we're interested in here is inserting the booking if possible.
--EDIT 1
Here's some code I'm trying. This is in the repository and is called from the controller. The problem with this is if multiple requests come in at once it can go over the limit of AvailableSpaces
public async Task<Models.Booking> AddBooking(Models.Booking booking)
{
var bookingEntity = _mapper.Map<Entities.Booking>(booking);
var e = await _context.Events.Include(x => x.Bookings).FirstOrDefaultAsync(c => c.Id == booking.EventId);
if (e.AvailableSpaces > 0)
{
_context.Add(bookingEntity);
}
await _context.SaveChangesAsync();
return _mapper.Map<Models.Booking>(bookingEntity);
}
--EDIT 2
I've tried using a transaction but this still fails for some reason and goes above the AvailableSpaces limit. Surely this should lock the row until the transaction is committed?
public async Task<Models.Booking> AddBooking(Models.Booking booking)
{
var bookingEntity = _mapper.Map<Entities.Booking>(booking);
var strategy = _context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
using (var transaction = _context.Database.BeginTransaction(IsolationLevel.Serializable))
{
try
{
var e = await _context.Events.Include(x => x.Bookings).FirstOrDefaultAsync(c => c.Id == booking.EventId);
if (e.AvailableSpaces == 0)
throw new Exception("No free spaces");
_context.Add(bookingEntity);
await _context.SaveChangesAsync();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
});
return _mapper.Map<Models.Booking>(bookingEntity);
}
--EDIT 3
public class Event
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Title { get; set; }
public int TotalSpaces { get; set; }
public int AvailableSpaces { get { return TotalSpaces - AllocatedSpaces; } }
public int AllocatedSpaces { get { return Bookings?.Count ?? 0; } }
[Timestamp]
public byte[] RowVersion { get; set; }
public string CourseCode { get; set; }
public virtual ICollection<Booking> Bookings { get; set; }
}
--EDIT 4
Added AvailableSpaces to database and persist this through which enables the concurrency check on the event, but feel this should not be required as I can work this out from the bookings assigned to the event.
public async Task<Models.Booking> AddBooking(Models.Booking booking)
{
if (booking == null)
throw new ArgumentNullException(nameof(booking));
var bookingEntity = _mapper.Map<Entities.Booking>(booking);
bool saveFailed;
var e = await _context.Events.Where(x => x.Bookings.Count < x.TotalSpaces).Include(x => x.Bookings).SingleOrDefaultAsync(c => c.Id == bookingEntity.EventId);
do
{
saveFailed = false;
try
{
if (e == null || e.AvailableSpaces == 0)
throw new Exception("No free spaces");
e.Bookings.Add(bookingEntity);
e.AvailableSpaces -= 1;
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
Console.WriteLine($"Concurrency Exception for event id: {booking.EventId}");
ex.Entries?.Single()?.Reload();
}
} while (saveFailed);
return _mapper.Map<Models.Booking>(bookingEntity);
}
Also tried setting the AvailableSpaces by doing a count of the bookings but then it stops working for some reason and goes over the limit on multiple calls.
e.AvailableSpaces = e.TotalSpaces - (e.Bookings?.Count() ?? 0);

How to update a single property in Entity Framework Core

I need to update only one or two properties of an Entity. In other words, I have an entity with an Id, ParentId, Name, and Description.
The issue is that when the name is updated, the Description is wiped out if it already existed in the database.
This is the code:
internal void Update(ItemInfo itemInfo)
{
var item = new Item { Id = itemInfo.Id, ParentId = itemInfo.ParentId, Name = itemInfo.Name };
var entry = this.DbContext.Items.Attach(item);
if (item.ParentId != null) entry.Property(x => x.ParentId).IsModified = true;
if (!(String.IsNullOrWhiteSpace(item.Name))) entry.Property(x => x.Name).IsModified = true;
this.SaveChanges();;
}
I thought that since I was setting the particular property as modified, only that property would be updated.
Or should I be getting the entity from the database first, and then just setting the property and saving. I wanted to avoid two trips to the database for a single save.
You can do following in order to update a single property:
internal void Update(ItemInfo itemInfo)
{
if (itemInfo == null) { throw new Exception($"item info was not supplied"); }
var itemInfoEntity = new ItemInfo()
{
Id = itemInfo.Id,
ParentId = itemInfo.ParentId,
Name = itemInfo.Name
};
dbContext.ItemInfo.Attach(itemInfoEntity);
dbContext.Entry(itemInfoEntity).Property(x => x.Id).IsModified = true;
dbContext.Entry(itemInfoEntity).Property(x => x.ParentId).IsModified = true;
dbContext.Entry(itemInfoEntity).Property(x => x.Name).IsModified = true;
dbContext.SaveChanges();
}
But if you only are updating few properties, then I think you should not send the whole object as parameter, just send the properties that needs to be updated something like this:
internal void Update(id, parentId, name)
{
...
}
The thing you're really after is Concurrency Handling. When multiple users are editting the same object at the same time, the second user will overwrite the changes of the first user.
Or should I be getting the entity from the database first, and then just setting the property and saving.
Yes, but having a controller method for each property of your object would be very tedious. That's why concurrency handling is the best option.
Also, your domain model should be entirely seperated from your database model. Never use entities in your web application directly. You are already doing so by having the Item entity (database model) and the ItemInfo class (domain model, used to handle the post-request).
Implement concurrency handling
First add a Timestamp column to your entity:
internal class Item
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
[Timestamp]
[ConcurrencyCheck]
public byte[] ConcurrencyStamp { get; set; }
}
Then, at the place where you update your entity:
[Controller]
public class ItemController : Controller
{
private readonly DbContext dbContext;
public ItemController(DbContext dbContext)
{
this.dbContext = dbContext;
}
[HttpPost]
//[Authorize]
//[ValidateAntiForgeryToken]
public async Task<ActionResult<ItemInfo>> Update([FromBody] ItemInfo item)
{
var existingItem = dbContext.Items.SingleOrDefaultAsync(i => i.Id == item.Id);
if (Convert.ToBase64String(existingItem.ConcurrencyStamp) != item.ConcurrencyStamp)
{
var databaseValue = new ItemInfo
{
Id = existingItem.Id,
ParentId = existingItem.ParentId,
Name = existingItem.Name,
};
return StatusCode(Microsoft.AspNetCore.Http.StatusCodes.Status409Conflict, databaseValue);
}
// Set new properties
existingItem.Id = item.Id;
existingItem.ParentId = item.ParentId;
existingItem.Name = item.Name;
// Save changes
await dbContext.SaveChangesAsync();
// Now return the updated item
// Now you should map the entity properties back to a new domain model...
var result = new ItemInfo
{
Id = existingItem.Id,
ParentId = existingItem.ParentId,
Name = existingItem.Name,
ConcurrencyStamp = Convert.ToBase64String(existingItem.ConcurrencyStamp),
};
return Ok(item);
}
}
Now when you try to update your item from the client-side, and receive a 409Conflict statuscode, you should decide how you want to handle this. I've chosen to display the database values below the respective input boxes.
You can find an entire implementation of this here

c# / Linq to SQL - Update statement not well formed (no field in SET, all in WHERE)

I like LinqToSQL because it is waaaayyy simpler to implement in a small project (versus EF) and avoids me to write/maintain SQL.
I have a lot of working Entity/tables but I face a problem with a particular class that use inheritance.
The SQL table "ChildClass" have only 3 fields : id, MyOtherField01 and MyOtherField02. A primary key is created on "id".
here is the class [simplified] :
[Table("ChildClass")]
public class ChildClass : ParentClass {
//constructor
public ChildClass(){}
//constructor that take the parent to inject property
public ChildClass(ParentClass ParamInject)
{ //code removed for simplicity
}
[Column(IsDbGenerated = true, Name = "id", IsPrimaryKey = true)]
public int NoUnique { get; set; }
[Column]
public bool MyOtherField01 { get; set; }
[Column]
public DateTime? MyOtherField02 { get; set; } }
}
Because of many reason, here is my insert/update mecanism that is able to ignore the context :
var builder = new StringBuilder();
try
{
using (var MyDBConnexion = new SqlConnection(MyDBConnStr))
{
//ouverture du context
using (BusinessContext MyDBContext = new BusinessContext(MyDBConnexion))
{
MyDBContext.Log = new System.IO.StringWriter(builder);
//If we have an insert...
if (param_Client.NoUnique == 0)
MyDBContext.ListOfChildClasses.InsertOnSubmit(MyChildClassInstance);
else //if we must update
{
//little funky work around that normally works well, that allow me to dont care about Context
var Existing = MyDBContext.ListOfChildClasses.Single(x => x.NoUnique == MyChildClassInstance.NoUnique);
//than, copy the properties into the "existing"
param_Client.CopyProperties(Existing);
}
MyDBContext.SubmitChanges();
}
}
}
catch (Exception ex)
{
string strT = builder.ToString(); //Here, I have the Update statement!
return strErr;
}
return "";
But for this precise class, here is the resulting SQL :
UPDATE ChildClass
SET
WHERE (id = #p1 and PrivilegeActif = #p2 and DateAdhesion = #p3)
No field in the set section!
any ideas?

EF6 Cannot insert duplicate key - I cannot find cause of this exception

I've got problem when I am trying to use method AddTimeSeriesDefinition(TimeSeries series) or AddTimeSeriesMetaData(TimeSeriesMetaData tsData) inside Parallel.ForEach()
I am struggling with this for couple of hours and I cannot believe that I cannot find any solution or even theoretical cause.
Inside my class Data which contains my db context DBEntity db = new DBEntity() I've got AddTimeSeriesDefinition(TimeSeries series) and AddTimeSeriesMetaData() methods :
public class Data : IDisposable
{
private DBEntity db;
public Data()
{
db = new DBEntity();
}
public TimeSeries AddTimeSeriesDefinition(TimeSeries series)
{
var timeSeries = db.TimeSeries.Where(ts => ts.Key1 == series.Key1 )
.Where(ts => ts.Key2 == series.Key2 )
.Where(ts => ts.Key3 == series.Key3 )
.FirstOrDefault();
if ( timeSeries == null )
{
timeSeries = db.TimeSeries.Add(series);
db.SaveChanges();
}
return timeSeries;
}
public void AddTimeSeriesMetaData(TimeSeriesMetaData tsData)
{
var tsd = db.TimeSeriesMetaData.Where(ts => ts.Key1 == tsData.Key1 )
.Where(ts => ts.Key2== tsData.Key2)
.FirstOrDefault();
if (tsd == null)
db.TimeSeriesMetaData.Add(tsData);
else
tsd.Value = tsData.Value;
try
{
db.SaveChanges();
}
catch (Exception ex)
{
Log.Error($"Error occurred (...) Key1:{tsData.Key1} Key2:{tsData.Key2}", ex);
}
}
Dispose()
{...}
}
However when I am using them in my main class, for example :
private MainClass
{
Data DB { get { value = new Data() } }
...
Parallel.ForEach( // arguments )
{
...
using( var db = DB )
{
db.AddTimeSeriesDefinition(timeSeries);
}
...
}
}
it's sometimes, totally randomly crashing in line
db.SaveChanges();
with exception :
Violation of PRIMARY KEY constraint 'PK_TimeSeriesMetaDatas'. Cannot insert duplicate key in object 'dbo.TimeSeriesMetaData'. The duplicate key value is ("Key1", "Key2"). The statement has been terminated.
For example my TimeSeriesMetaData EF class:
[Table("TimeSeriesMetaData")]
public partial class TimeSeriesMetaData
{
[Key]
[Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Key1 { get; set; }
[Key]
[Column(Order = 1)]
public string Key2 { get; set; }
[Required]
public string Key3 { get; set; }
}
I've read that creating Entity Framework DBContext each time for each operation should be also Thread-safe.
What can be cause of this problem if I've always checked if record exist?
I will appreciate any help.
The problem is that DbSet is not ThreadSafe. You are having a run condition withing your Parallel.ForEach loop. You have to lock your call to both of your methods. https://msdn.microsoft.com/en-us/library/c5kehkcz.aspx
Hope it helps

How to create a SQLite database UWP

I'm new in creating Data Bases and I should to create a SQLite DB, which contains Buses, every Bus contains a list of Stops and every Stop contains a timetable. I have create a class Buses:
class Buses
{
[PrimaryKey, AutoIncrement]
public string number { get; set; }
public List<Stop> stops = new List<Stop>();
}
public class Stop
{
public string StopName { get; set; }
public string Timetable { get; set; }
}
And I don't know how should I add stops to the DB. I trying something like this:
private void Add_Click(object sender, RoutedEventArgs e)
{
var s = conn.Insert(new Buses()
{
number = Id.Text,
stops = stops.Add(new Stop { StopName = StopName.Text, Timetable = Time.Text });
}
But I get error
The name 'stops' does not exist in the current context
I understand, why there such error is, but I don't know, how to fix it. It is possible, that there are easiest ways to adding such constructions to the DB.
I'm new in creating Data Bases and I should to create a SQLite DB, which contains Buses, every Bus contains a list of Stops and every Stop contains a timetable.
You cannot create a table with a column to store a list of object in SQLite. Because there is no such datatype supported in SQLite. So the Buses table you have created will never store its stop list into SQLite.
Based on my understanding, a bus can have many stops, and a stop is also used for many buses, the relationship between bus and stop should be Many-to-Many. So you may need to create another relationship table in addition to table Buses and table Stop. As both table Buses and table Stop are very simple in your scenario, I just create one relationship table here (it makes it simpler and also works but may cause redundancy):
class Buses
{
public string number { get; set; }
public string StopName { get; set; }
public string Timetable { get; set; }
}
Use the following code to add the stop:
private async void btnAddStop_Click(object sender, RoutedEventArgs e)
{
// check if the stop is already added for the bus
List<Buses> buses = new List<Buses>();
buses = LocalDatabase.GetStopListByBusNumberAndStopName(Id.Text, StopName.Text);
if (buses.ToArray().Length > 0)
{
await new MessageDialog("Cannot add this stop because the stop is already added for the bus!").ShowAsync();
}
else
{
Buses b = new Buses();
b.number = Id.Text;
b.StopName = StopName.Text;
b.Timetable = Timetable.Text;
// add the stop to db
LocalDatabase.InsertStopToDatabase(b);
await new MessageDialog("The stop is added successfully!").ShowAsync();
}
}
// get the buses by bus number
public static List<Buses> GetStopListByBusNumber(string busNumber)
{
List<Buses> results = new List<Buses>();
using (SQLite.Net.SQLiteConnection conn = new SQLite.Net.SQLiteConnection(new SQLite.Net.Platform.WinRT.SQLitePlatformWinRT(), DBPath))
{
results = conn.Query<Buses>("SELECT * FROM Buses WHERE number = ?", busNumber);
}
return results;
}
// get the buses by bus number and stop name
public static List<Buses> GetStopListByBusNumberAndStopName(string busNumber, string stopName)
{
List<Buses> results = new List<Buses>();
using (SQLite.Net.SQLiteConnection conn = new SQLite.Net.SQLiteConnection(new SQLite.Net.Platform.WinRT.SQLitePlatformWinRT(), DBPath))
{
results = conn.Query<Buses>("SELECT * FROM Buses WHERE number = ? AND StopName = ?", busNumber, stopName);
}
return results;
}
Then use the following code to retrieve the stop list for a bus:
// get the buses by bus number
public static List<Buses> GetStopListByBusNumber(string busNumber)
{
List<Buses> results = new List<Buses>();
using (SQLite.Net.SQLiteConnection conn = new SQLite.Net.SQLiteConnection(new SQLite.Net.Platform.WinRT.SQLitePlatformWinRT(), DBPath))
{
results = conn.Query<Buses>("SELECT * FROM Buses WHERE number = ?", busNumber);
}
return results;
}
Here is the entire sample for your reference.
The problem is the way fast object initializes work in .Net
var s = conn.Insert(new Buses()
{
number = Id.Text,
stops = stops.Add(new Stop { StopName = StopName.Text, Timetable = Time.Text });
}
Inside the initialize block - You cannot get the values your other members.
(Even if you could get the member value, the function stops.Add is void - which does not return a new list)
If you want to use the quick initialize way:
var s = conn.Insert(new Buses()
{
number = Id.Text,
stops = new List<Stop>()
{
new Stop { StopName = StopName.Text, Timetable = Time.Text }),
}
}
Or you can do it without:
private void Add_Click(object sender, RoutedEventArgs e)
{
var bus = new Buses()
{
number = Id.Text,
};
bas.stops.Add(new Stop { StopName = StopName.Text, Timetable = Time.Text });
var s = conn.Insert(bus);
}

Categories

Resources