I'm quite a newbie with Linq to Sql, and I'm facing an issue regarding accessing a foreign entity.
Here is the related DB :
Table MyClass with two columns : Id, ProducerId
Table Person with two columns : Id, Affix
Here is my partial class :
public partial class MyClass
{
public string ProducerAffix
{
get { return Producer.Affix; }
}
}
And the dbml designer file where the Producer property is generated related to ProducerId foreign key :
[global::System.Data.Linq.Mapping.AssociationAttribute(Name="Person_MyClass1", Storage="_Person1", ThisKey="ProducerId", OtherKey="Id", IsForeignKey=true)]
public Person Producer
{
get
{
return this._Person1.Entity;
}
set
{
Person previousValue = this._Person1.Entity;
if (((previousValue != value)
|| (this._Person1.HasLoadedOrAssignedValue == false)))
{
this.SendPropertyChanging();
if ((previousValue != null))
{
this._Person1.Entity = null;
previousValue.MyClass.Remove(this);
}
this._Person1.Entity = value;
if ((value != null))
{
value.MyClass.Add(this);
this.ProducerId = value.Id;
}
else
{
this.ProducerId = default(System.Guid);
}
this.SendPropertyChanged("Producer");
}
}
}
When accessing MyClass' Affix property, an ObjectDisposedException is thrown...
Do I need to open a Datacontext when accessing the property ?
I read this post LINQ to SQL ObjectDisposedException on entity that never asked for but really would like avoiding creating a ViewModel...
Is there any other solution ?
Thanks a lot !
EDIT
Following JAT's answer I tried to use the DLO but don't really know how to return my foreign value from it... I found this tutorial (http://www.codeproject.com/Articles/37857/Optimizing-LINQ-Queries-using-DataLoadOptions), do I have to write a query then ?
public string Affix
{
get
{
using (var db = new DBDataContext())
{
var dlo = new DataLoadOptions();
dlo.LoadWith<Person>(p => p.Affix);
db.LoadOptions = dlo;
...
return Producer.Affix;
}
}
}
For those who might face the same issue later, I finally found out where this came from.
When I added my Person and my MyClass, I used this function :
public static Person Add(Person person)
{
using (var db = new DBDataContext())
{
db.Person.InsertOnSubmit(person);
db.SubmitChanges();
return person;
}
}
Removing the "using" did the trick for me and now I can access my foreign keys' entities.
I sincerely don't understand why because I read that "using" was a better solution than "new" because of the close issue, but seems like it does not work correctly with it, so I removed it.
public static Person Add(Person person)
{
var db = new DBDataContext();
db.Person.InsertOnSubmit(person);
db.SubmitChanges();
return person;
}
Related
i have transform a php/js code to js/c#, but i stuck for update the new value.
The php code is :
`if (isset($_POST['update'])) {
foreach($_POST['positions'] as $position) {
$index = $position[0];
$newPosition = $position[1];
$conn->query("UPDATE country SET position = '$newPosition' WHERE id='$index'");
}
exit('success');
}`
My "empty" c# code
[HttpPost]
public ActionResult Index (userTable index)
{
picturesEntities MyDb = new picturesEntities();
homeViewModel HVM = new homeViewModel();
HVM.userTables = MyDb.userTables.ToList();
if (Request["update"] != null)
{
foreach (Request["positions"])
{
MyDb.SaveChanges();
}
return View(HVM);
}
}
If someone could help me for it that would be great, i'm stuck on it for days and i didn't find a workning solution yet.
Thanks to everyone who read my message.
Most ASP.NET will bind a custom class which will be compatible to your request.
public class UserPositionsRequest
{
public bool Update { get; set; }
// For orderly, this actually be a list of a custom class
public List<int[]> Positions { get; set; }
}
This by any means is not a complete and working solution, the following code was never been tested and can be consider as pseudo-like code.
Also, the .Id and .Position should be the same sensitivity as in Db.
// Binding our UserPositionsRequest class
public void Index(UserPositionsRequest request) {
// Checking if we should update, if you will change the request to boolean type: "true"
// ..on the client side, then you could actually change the condition to be: if (request.Update)
if (request.Update == 1) {
// Creating database connection using (I assume) EntityFramework
using (var picturesEntities = new picturesEntities()) {
// Building a dictionary for fast lookup. Key, Value as the 0, 1 arg respectfully
var usersDataToUpdate = request.Positions.ToDictionary(p => p[0], p => p[1]);
// Finding the entries that needs to be updated
var usersEntitiesToUpdate = picturesEntities.userTables.Where(cntry => usersDataToUpdate.ContainsKey(cntry.Id));
// Iterating over the entities
foreach (var userEntity in usersEntitiesToUpdate) {
// Updating their position.
userEntity.Position = usersDataToUpdate[userEntity.Id];
}
picturesEntities.SaveChanges();
}
}
// Probably you wanted to return something here, but it's probably an ajax and you can skip that.
}
I am using Entity Framework 6.1 with an entity model that consists of three entities so far, a Product, ProductImage and File. However, whilst my data is being persisted to the database successfully, I'm having some difficulties with loading the data using eager loading via the Include() method. The entities I'm using are as follows.
Product entity:
public class Product
{
ICollection<ProductImage> images;
public Product ()
{
images = new List<ProductImage>();
}
public ICollection<ProductImage> Images
{
get { return images; }
set { images = value; }
}
}
File entity:
public class File
{
private Guid fileId;
public File()
{
FileId = Guid.NewGuid();
}
public Guid FileId
{
get { return fileId; }
set { fileId = value; }
}
}
ProductImage entity:
public class ProductImage
{
private File file;
private Product product;
private ProductImage()
{
file = new File();
product = new Product();
}
public ProductImage(Product product, File file)
{
this.product = product;
this.file = file;
}
[Key]
[Column(Order = 0)]
[ForeignKey("Product")]
public int ProductId
{
get { return product.ProductId; }
set { product.ProductId = value; }
}
[Key]
[Column(Order = 1)]
[ForeignKey("File")]
public Guid FileId
{
get { return file.FileId; }
set { file.FileId = value; }
}
public File File
{
get { return file; }
set { file = value; }
}
[InverseProperty("Images")]
public Product Product
{
get { return product; }
set { product = value; }
}
}
I then attempt to utilise the following function to load a Product, its ProductImage and the related File using the following:
static Product LoadProduct(int productId)
{
using (var db = new ProductCatalogueDbContext())
{
var query = from product in db.Products
.Include(p => p.Images.Select(i => i.File))
.Where(p => p.ProductId == productId)
select product;
return query.SingleOrDefault();
}
}
Is there are a problem with the query I'm using?
Yes, there is a problem with your code - you're freely mixing the SQL-like style of a LINQ query with the "dot notation" - you cannot do it like that....
I personally prefer the "dot-notation" - and in that case, you need to use that notation for all parts of your LINQ query:
var query = db.Products
.Include(p => p.Images.Select(i => i.File))
.Where(p => p.ProductId == productId)
.Select(p => p);
As far a I know, the Include doesn't have a "SQL-like" equivalent, so you would either have to "combine" it properly with the SQL-like syntax, or just use the "dot notation" all the way....
Whilst I don't fully comprehend the full reasons why the data wasn't loading it turns out I was able to identify in part the cause. Instantiation of a File object in the Ctor of ProductImage and assignment of its reference to the file field, which was being returned by the File property, was causing problems.
I've encountered problems with EF loading data before when the property to be loaded doesn't actually return a null. The problem was encountered in relation to a lazy loaded entity. From what I can deduce, although this may be mistaken, it appears that unless the property returns null, EF will not actually load data from the database that is related to the property?
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
For example, take the following database tables: Students, Courses, and StudentsCourses.
How do I, in Entity Framework, make sure when adding a new course for a student, that the course doesn't already exist? Which means to me, checking StudentCourses table.
Do I need to write straight sql to check that?
using (var context = new StudentContext()
{
var alreadyExists = context.StudentsCourses.Any(x => x.StudentId == studentId && x.CourseId == courseId);
}
If you have a simple many to many relationship, you may not have a StudentsCourse entity. I like this pattern for adding to a many to many relationship:
public Student
{
private _Courses = new List<Course>();
public int ID { get; set; }
public virtual ICollection Courses
{
get { return _Courses; }
protected set { _Courses = value; }
}
public void AddCourse(Course course)
{
//And you can add your duplicate check here
if(!Courses.Any(c => c.ID == course.ID))
Courses.Add(course);
}
}
Unfortunately the Courses property is not a read-only collection so it doesn't prevent someone bypassing that method elsewhere with:
student.Courses.Add(course);
But then the methods suggested in the other answers don't prevent that either
Create a method:
public bool IsStudentOnCourse(int studentId, int courseId)
{
using (var db = new DBContext()) //replace for real context..
{
return db.StudentsCourses.Any(x => x.StudentId == studentId && x.CourseId == courseId);
}
}
BenjaminPaul's answer works very wel. Another option is to try and retrieve your student, and if it does not exist, create a new one.
You could create a method like this
public StudentCourse CreateOrUpdate(VM_StudentCourse studentCourse)
{
StudentCourse dbStudentCourse;
using (var context = new StudentContext()
{
dbStudentCourse = context.StudentsCourses.FirstOrDefault(x => x.StudentId == studentCourse.studentId && x.CourseId == studentCourse.courseId);
If (dbStudentCourse == null)
{
dbStudent = new StudentCourse();
dbStudent.StudentId = studentCourse.StudentId;
dbStudent.CourseId = studentCourse.CourseId;
context.Add(dbStudent);
}
dbStudent.OtherProperty1 = studentCourse.SomeProp;
dbStudent.OtherProperty2 = studentCourse.SomeOtherProp;
context.SaveChanges();
}
return dbStudentCourse;
}
I'm using EF code first. I'm using a base Repository for all my repositories and an IUnitofWork that inject to the repositories, too:
public interface IUnitOfWork : IDisposable
{
IDbSet<TEntity> Set<TEntity>() where TEntity : class;
int SaveChanges();
}
public class BaseRepository<T> where T : class
{
protected readonly DbContext _dbContext;
protected readonly IDbSet<T> _dbSet;
public BaseRepository(IUnitOfWork uow)
{
_dbContext = (DbContext)uow;
_dbSet = uow.Set<T>();
}
//other methods
}
e.g my OrderRepository is like this:
class OrderRepository: BaseRepository<Order>
{
IUnitOfWork _uow;
IDbSet<Order> _order;
public OrderRepository(IUnitOfWork uow)
: base(uow)
{
_uow = uow;
_order = _uow.Set<Order>();
}
//other methods
}
And I use it in this way:
public void Save(Order order)
{
using (IUnitOfWork uow = new MyDBContext())
{
OrderRepository repository = new OrderRepository(uow);
try
{
repository.ApplyChanges<Order>(order);
uow.SaveChanges();
}
}
}
Is there any way to log change histories of all entities(include their navigation properties) during .SaveChanges()? I want to log original values(before save occurs) and changed values(after save occurs).
You can get the before and after values for all changed entities by going through DbContext.ChangeTracker. Unfortunately the API is a little verbose:
var changeInfo = context.ChangeTracker.Entries()
.Where (t => t.State == EntityState.Modified)
.Select (t => new {
Original = t.OriginalValues.PropertyNames.ToDictionary (pn => pn, pn => t.OriginalValues[pn]),
Current = t.CurrentValues.PropertyNames.ToDictionary (pn => pn, pn => t.CurrentValues[pn]),
});
You can modify that to include things like the type of the entity if you need that for your logging. There is also a ToObject() method on the DbPropertyValues (the type of OriginalValues and CurrentValues) you could call if you already have a way to log whole objects, although the objects returned from that method will not have their navigation properties populated.
You can also modify that code to get all entities in the context by taking out the Where clause, if that makes more sense given your requirements.
I have overridded the default SaveChanges method to log changes for add/update/delete in entity. Though it does not cover navigation property changes.
Based on this article: Using entity framework for auditing
public int SaveChanges(string userId)
{
int objectsCount;
List<DbEntityEntry> newEntities = new List<DbEntityEntry>();
// Get all Added/Deleted/Modified entities (not Unmodified or Detached)
foreach (var entry in this.ChangeTracker.Entries().Where
(x => (x.State == System.Data.EntityState.Added) ||
(x.State == System.Data.EntityState.Deleted) ||
(x.State == System.Data.EntityState.Modified)))
{
if (entry.State == System.Data.EntityState.Added)
{
newEntities.Add(entry);
}
else
{
// For each changed record, get the audit record entries and add them
foreach (AuditLog changeDescription in GetAuditRecordsForEntity(entry, userId))
{
this.AuditLogs.Add(changeDescription);
}
}
}
// Default save changes call to actually save changes to the database
objectsCount = base.SaveChanges();
// We don't have recordId for insert statements that's why we need to call this method again.
foreach (var entry in newEntities)
{
// For each changed record, get the audit record entries and add them
foreach (AuditLog changeDescription in GetAuditRecordsForEntity(entry, userId, true))
{
this.AuditLogs.Add(changeDescription);
}
// TODO: Think about performance here. We are calling db twice for one insertion.
objectsCount += base.SaveChanges();
}
return objectsCount;
}
#endregion
#region Helper Methods
/// <summary>
/// Helper method to create record description for Audit table based on operation done on dbEntity
/// - Insert, Delete, Update
/// </summary>
/// <param name="dbEntity"></param>
/// <param name="userId"></param>
/// <returns></returns>
private List<AuditLog> GetAuditRecordsForEntity(DbEntityEntry dbEntity, string userId, bool insertSpecial = false)
{
List<AuditLog> changesCollection = new List<AuditLog>();
DateTime changeTime = DateTime.Now;
// Get Entity Type Name.
string tableName1 = dbEntity.GetTableName();
// http://stackoverflow.com/questions/2281972/how-to-get-a-list-of-properties-with-a-given-attribute
// Get primary key value (If we have more than one key column, this will need to be adjusted)
string primaryKeyName = dbEntity.GetAuditRecordKeyName();
int primaryKeyId = 0;
object primaryKeyValue;
if (dbEntity.State == System.Data.EntityState.Added || insertSpecial)
{
primaryKeyValue = dbEntity.GetPropertyValue(primaryKeyName, true);
if(primaryKeyValue != null)
{
Int32.TryParse(primaryKeyValue.ToString(), out primaryKeyId);
}
// For Inserts, just add the whole record
// If the dbEntity implements IDescribableEntity,
// use the description from Describe(), otherwise use ToString()
changesCollection.Add(new AuditLog()
{
UserId = userId,
EventDate = changeTime,
EventType = ModelConstants.UPDATE_TYPE_ADD,
TableName = tableName1,
RecordId = primaryKeyId, // Again, adjust this if you have a multi-column key
ColumnName = "ALL", // To show all column names have been changed
NewValue = (dbEntity.CurrentValues.ToObject() is IAuditableEntity) ?
(dbEntity.CurrentValues.ToObject() as IAuditableEntity).Describe() :
dbEntity.CurrentValues.ToObject().ToString()
}
);
}
else if (dbEntity.State == System.Data.EntityState.Deleted)
{
primaryKeyValue = dbEntity.GetPropertyValue(primaryKeyName);
if (primaryKeyValue != null)
{
Int32.TryParse(primaryKeyValue.ToString(), out primaryKeyId);
}
// With deletes use whole record and get description from Describe() or ToString()
changesCollection.Add(new AuditLog()
{
UserId = userId,
EventDate = changeTime,
EventType = ModelConstants.UPDATE_TYPE_DELETE,
TableName = tableName1,
RecordId = primaryKeyId,
ColumnName = "ALL",
OriginalValue = (dbEntity.OriginalValues.ToObject() is IAuditableEntity) ?
(dbEntity.OriginalValues.ToObject() as IAuditableEntity).Describe() :
dbEntity.OriginalValues.ToObject().ToString()
});
}
else if (dbEntity.State == System.Data.EntityState.Modified)
{
primaryKeyValue = dbEntity.GetPropertyValue(primaryKeyName);
if (primaryKeyValue != null)
{
Int32.TryParse(primaryKeyValue.ToString(), out primaryKeyId);
}
foreach (string propertyName in dbEntity.OriginalValues.PropertyNames)
{
// For updates, we only want to capture the columns that actually changed
if (!object.Equals(dbEntity.OriginalValues.GetValue<object>(propertyName),
dbEntity.CurrentValues.GetValue<object>(propertyName)))
{
changesCollection.Add(new AuditLog()
{
UserId = userId,
EventDate = changeTime,
EventType = ModelConstants.UPDATE_TYPE_MODIFY,
TableName = tableName1,
RecordId = primaryKeyId,
ColumnName = propertyName,
OriginalValue = dbEntity.OriginalValues.GetValue<object>(propertyName) == null ? null : dbEntity.OriginalValues.GetValue<object>(propertyName).ToString(),
NewValue = dbEntity.CurrentValues.GetValue<object>(propertyName) == null ? null : dbEntity.CurrentValues.GetValue<object>(propertyName).ToString()
}
);
}
}
}
// Otherwise, don't do anything, we don't care about Unchanged or Detached entities
return changesCollection;
}
you have scared people away with the extra requirement
Include their navigation properties
This is simply a non trivial exercise.
And if this is important, you should manage/track changes across references with code.
this is a sample covering this topic
Undo changes in entity framework entities
There is a sample doing close top what you want here
undo changes
It can easily be converted to load before and after images elsewhere.
Given the ObjectState entry after DetectChanges is called, you can implement a simple entity by entity option. and per UOW. But the navigation / references version makes this very complex as you worded the requirement.
EDIT : How to access the changeList
public class Repository<TPoco>{
/....
public DbEntityEntry<T> Entry(T entity) { return Context.Entry(entity); }
public virtual IList<ChangePair> GetChanges(object poco) {
var changes = new List<ObjectPair>();
var thePoco = (TPoco) poco;
foreach (var propName in Entry(thePoco).CurrentValues.PropertyNames) {
var curr = Entry(thePoco).CurrentValues[propName];
var orig = Entry(thePoco).OriginalValues[propName];
if (curr != null && orig != null) {
if (curr.Equals(orig)) {
continue;
}
}
if (curr == null && orig == null) {
continue;
}
var aChangePair = new ChangePair {Key = propName, Current = curr, Original = orig};
changes.Add(aChangePair);
}
return changes;
}
///... partial repository shown
}
// FYI the simple return structure
public class ChangePair {
public string Key { get; set; }
public object Original { get; set; }
public object Current { get; set; }
}
DbContext has ChangeTracker property.
You can override .SaveChanges() in your context and log changes.
I don't think that entity framework can do it for you. Probably, you must detect changes directly in your model classes.
I've expanded on Steve's answer to provide a check for Changed, Added, and Deleted entities and print them in a sensible way.
(My use case is to ensure there are no unsaved changes before disposing of a DbContext instance, but this check could be done at any point)
/// <summary>Helper method that checks whether the DbContext had any unsaved changes before it was disposed.</summary>
private void CheckForUnsavedChanges(DbContext dbContext)
{
try
{
List<DbEntityEntry> changedEntityEntries = dbContext.ChangeTracker.Entries()
.Where(t => t.State != EntityState.Unchanged && t.State != EntityState.Detached).ToList();
if (!changedEntityEntries.Any())
return;
throw new Exception("Detected that there were unsaved changes made using a DbContext. This could be due to a missing call to `.SaveChanges()` or possibly " +
"some read-only operations that modified the returned entities (in which case you might wish to use `.AsNoTracking()` in your query). Changes:\n " +
String.Join("\n ", changedEntityEntries.Select(entry => $"{entry.Entity.GetType()} {entry.State}:\n " + String.Join("\n ",
entry.State == EntityState.Modified ? entry.CurrentValues.PropertyNames
// Only output properties whose values have changed (and hope they have a good ToString() implementation)
.Where(pn => entry.OriginalValues?[pn] != entry.CurrentValues[pn])
.Select(pn => $"{pn} ({entry.OriginalValues?[pn]} -> {entry.CurrentValues[pn]})") :
// Added or Deleted entities are output in their entirety
entry.State == EntityState.Added ? entry.CurrentValues.PropertyNames.Select(pn => $"{pn} = {entry.CurrentValues[pn]}") :
/* entry.State == EntityState.Deleted ? */ entry.CurrentValues.PropertyNames.Select(pn => $"{pn} = {entry.OriginalValues[pn]}")))));
}
catch (Exception ex)
{
_logger.Error("Issue encountered when checking for unsaved changes.", ex);
}
}