I'm trying to create an update method in a generic repository as a LINQ to SQL data access layer.
I have an entity like this:
[Table]
public class Product
{
[Column(IsPrimaryKey = true, IsDbGenerated = true,
DbType = "Int NOT NULL IDENTITY")]
public int Id { get; private set; }
[Column(UpdateCheck = UpdateCheck.Never)]
public string Name { get; set; }
....
}
I set Update Check = true for all the fields exept for the id as #jeff Atwood suggests in this post and I set the asModified propery in the attach method to true which i found in this post as following:
public void Update(T entity)
{
_db.GetTable<T>().Attach(entity, true);
_db.SubmitChanges();
}
but I keep getting the same exception:
An entity can only be attached as modified without original state if
it declares a version member or does not have an update check policy.
So what's the problem ???
Do you recommend any other approaches to create an update method in a generic repository except creating a timestamp column as a version number.
We used the following code in our DAO to solve the same issue:
(e.g. only, don't have the real code on me at the moment)
public void UpdateUser(tblUser user)
{
WriteDataContect.Attach
(
user,
ReadOnlyDataContext.tblUsers
.Select(o => o.UserId == user.UserId)
);
WriteDataContext.SubmitChanges();
}
ReadOnlyDataContext has TrackChanges = false;
We couldn't find another solution based on our needs, without having to write alot of plumbing code.
Modifying the database to accommodate LinqToSql's need for a timestamp column wasn't an option for us either.
The additional DB call didn't create any issues in our testing.
You can read the entity from db and copy the fields. I don't like the approach but when dealing with the similar problems we had to do it.
You never know if the object hasn't been loaded into the context before and if it was you will get an exception when attaching it anyway. At least it is the case for EF but it would be logical for linq for sql too.
Related
I am trying to update an entity using Entity Framework and save it to the database. When the update is called, my service method retrieves the DTO, assigns its the values of the entity object that the UI passed to it, and then saves it to the database. Instead of manually assigning those values, i'd like to use Automapper, however when I do this, the values that I am not mapping are updated to null. Is there a way in Entity Framework or Automapper to prevent this?
Service method finds the existing object in the database, assigns the new entity's properties to it, then saves:
public void Update(MyEntity updatedEntity, int id)
{
var existingObject = db.tblmyentity.Find(id);
existingObject.name = updatedEntity.name;
existingObject.address = updatedEntity.address;
existingObject.phone = updatedEntity.phone;
db.SaveChanges();
}
However, there are values stored in fields of this object not accessible by the UI, such as who modified the object and when. Using AutoMapper to simplify this code (shown below) causes these fields to update to null:
public void Update(MyEntity updatedEntity, int id)
{
var existingObject = db.tblmyentity.Find(id);
Mapper.Map(updatedEntity, existingObject);
db.SaveChanges();
}
A good practice is to create a (service, api) model that contains only the relevant properties that can be updated. E.g.:
public class MyEntityServiceModel
{
public string name { get; set; }
public string address { get; set; }
public string phone { get; set; }
}
// this looks differently in recent versions of AutoMapper, but you get the idea
Mapper.CreateMap<MyEntityServiceModel, MyEntity>();
// your update functions looks the same, except that it receives a service model, not a data model
Update(MyEntityServiceModel updatedEntity, int id)
{
// same code here
}
This approach has the following advantages:
you obtain what you are asking for
safety: you do not risk updating more properties than you should since the service model clearly specify the properties that should be updated
serialization: the service model is more appropriate if you need serialization (EF models may include unwanted navigation properties)
Update function consumer becomes unaware of the data persistence library you are using.
I have a simple code in Entity Framework (EF) v4.1 code first:
PasmISOContext db = new PasmISOContext();
var user = new User();
user.CreationDate = DateTime.Now;
user.LastActivityDate = DateTime.Now;
user.LastLoginDate = DateTime.Now;
db.Users.Add(user);
db.SaveChanges();
user.Avatar = new Avatar() { Link = new Uri("http://myUrl/%2E%2E/%2E%2E") };
db.SaveChanges();
db.Users.Add(new User() { Avatar = new Avatar() { Link = new Uri("http://myUrl/%2E%2E/%2E%2E") } });
db.SaveChanges();
The problem is that I get an error
An error occurred while saving entities that do not expose foreign key
properties for their relationships. The EntityEntries property will
return null because a single entity cannot be identified as the source
of the exception. Handling of exceptions while saving can be made
easier by exposing foreign key properties in your entity types. See
the InnerException for details.
at
db.Users.Add(new User() { Avatar = new Avatar() { Link = new Uri("http://myUrl/%2E%2E/%2E%2E") } });
db.SaveChanges();
I don't understand why the similar operation works. Is there something wrong with my model, or with ef-code-first?
public class Avatar
{
[Key]
public int Id { get; set; }
[Required]
public string LinkInString { get; set; }
[NotMapped]
public Uri Link
{
get { return new Uri(LinkInString); }
set { LinkInString = value.AbsoluteUri; }
}
}
public class User
{
[Key]
public int Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public Avatar Avatar { get; set; }
public virtual ICollection<Question> Questions { get; set; }
public virtual ICollection<Achievement> Achievements { get; set; }
public DateTime CreationDate { get; set; }
public DateTime LastLoginDate { get; set; }
public DateTime LastActivityDate { get; set; }
}
For those of you who would still have this error with all keys properly defined, have a look at your entities and make sure you don't leave a datetime field with a null value.
This error message can be thrown for any kind of reason. The 'InnerException' property (or its InnerException, or the InnerException of that, etc) contains the actual primary cause of the problem.
It would of course be useful to know something about where the problem occurred - which object(s) in the unit of work is causing the problem? The exception message would normally tell you in the 'EntityEntries' property, but in this case, for some reason, that can't be done. This diagnostic complication - of the 'EntityEntries' property being empty - is apparently because some Entities 'do not expose foreign key properties for their relationships.'
Even if the OP gets the error because of failing to initialize DateTimes for the second instance of User, they get the diagnostic complication - 'EntityEntries' being empty, and a confusing top-level message ... because one of their Entity's doesn't 'expose foreign key properties'. To fix this, Avatar should have a public virtual ICollection<User> Users { get; set; } property defined.
The issue was resolved by adding an FK property.
In my case the following situation was giving me the same Exception:
Imagine a code first EF model where you have a Garage entity that has a collection of Car entities. I needed to remove a car from the garage so I ended up with code that looked like this:
garageEntity.Cars.Remove(carEntity);
Instead, it should've been looked like this:
context.Cars.Remove(carEntity);
Just for others who might have similar problems. I had the same error, but for a different reason. In one of the child objects I defined the [Key] as being a value which was the same for different saves. A stupid mistake on my part, but the error message does not instantly lead you to the problem.
In my case the exeception was thrown because EF had created a migration incorrectly.
It missed setting the identity: true on the second table. So go into the migrations which created the relevant tables and check if it missed to add identity.
CreateTable(
"dbo.LogEmailAddressStats",
c => new
{
Id = c.Int(nullable: false, identity: true),
EmailAddress = c.String(),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.LogEmailAddressStatsFails",
c => new
{
Id = c.Int(nullable: false), // EF missed to set identity: true!!
Timestamp = c.DateTime(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.LogEmailAddressStats", t => t.Id)
.Index(t => t.Id);
An Id column should have identity (i.e. auto-incrementing!) so this must be a EF bug.
You could add identity manually with SQL directly to the database but I prefer using Entity Framework.
If you run in to the same problem I see two easy solutions:
Alt 1
reverse the incorrectly created migration with
update-database -target:{insert the name of the previous migration}
Then add the identity: true manually to the migration code and then update-database again.
Alt 2
you create a new migration that adds identity. If you have no changes in the models and you run
add-migration identity_fix
it will create an empty migration. Then just add this
public partial class identity_fix : DbMigration
{
public override void Up()
{
AlterColumn("dbo.LogEmailAddressStatsFails", "Id", c => c.Int(nullable: false, identity: true));
}
public override void Down()
{
AlterColumn("dbo.LogEmailAddressStatsFails", "Id", c => c.Int(nullable: false));
}
}
This problem can also arise from reversed key declarations. If you're using fluent to configure the relationship, make sure the left and right keys are mapped to the correct entity.
I hade same probleme. in my case, it was due to datetime field with a null value. I had to passe a value to datetime and evrythings went fine
Another answer:
I used this:
public List<EdiSegment> EdiSegments { get; set; }
instead of this:
public virtual ICollection<EdiSegment> EdiSegments { get; set; }
and got the error message noted above.
I had the same error and in my case the problem was that I added a relationship object which had already been loaded "AsNoTracking". I had to reload the relation property.
BTW, Some suggest using "Attach" for relations that already exist in db, I haven't tried that option though.
In my case, the problem was that I renamed a column improperly, so the migration made two columns, one called "TeamId" and one called "TeamID". C# cares, SQL doesn't.
Yet another different case here.
A query was cast to a list and while doing that, it created entities by their constructor for comparison in the linq expression right after the ToList(). This created entities that gotten into the deleted state after the linq expression finished.
However! There was a small adjustment that created another entity in the constructor, so that this new entity got linked to an entity that was marked as Deleted.
Some code to illustrate:
query.Except(_context.MyEntitySetSet()
.Include(b => b.SomeEntity)
.Where(p => Condition)
.ToList() // This right here calls the constructor for the remaining entities after the where
.Where(p => p.Collection.First(b => Condition).Value == 0)
.ToList();
The constructor of MyEntity:
public partial class MyEntity
{
protected MyEntity()
{
// This makes the entities connected though, this instance of MyEntity will be deleted afterwards, the instance of MyEntityResult will not.
MyEntityResult = new MyEntityResult(this);
}
}
My solution was to make sure the entire expression was done inside the IQueryable so that there won't be any objects created.
I'm not entirely sure that it's going to help in your case because I'm setting up my tables using Fluent API, however, as far I can tell, the issue arises regardless whether the schema is set up using data annotations (attributes) or Fluent API (configuration).
There seems to be a bug in EF (v. 6.1.3) as it omits certain changes to the schema when updating the DB to the next migration. The quickest route around it is (during the development stage) to remove all the tables from the DB and runt migrations from init stage again.
If you're already in production, the quickest solution I've found was to manually change the schema in the DB or, if you want to have version control of the changes, manually manipulate the methods Up() and Down() in your migration.
Today I faced this issue and tried the possible solutions posted above but none of them helped me. I had UnitOfWork pattern implemented and system was committing the data in last after adding all the records.
In my case system was combining the two models and querying the DB
Invalid object name 'dbo.RoleModelUserModel'.
where these were two different models actually.
I fixed this by reordering the insert statements and adding the parent entity first. In this case added the user first and issue resolved.
After a bit of investigation I found that whilst .Net supports a minimum date (DateTime.MinValue) of 01/01/0001 00:00:00 and a maximum (DateTime.MaxValue) of 31/12/9999 23:59:59 in SQL Server Compact Edition minimum date is 01/01/1753 00:00:00.
When I entered a date greater than 01/01/1753 00:00:00, this error disappeared.
Is your application or website being accessed from some third party application when this error is coming? If yes, then please check the access rights of the account which is sending the request to your application.
In our case, it was ServiceNow MID server service which was the culprit. It is a Windows service. If you want to know more about it then please read this link. So basically, you need to check two things:
Under the context of which account the calling service should run to access your application?
What all access rights are needed for the service's log on account to do all allowed operations in your application?
As per this article of ServiceNow we had to give Log on as a service right to the MID Server service's log on account. You can do it via in Local Security Policies console (Refer screenshot).
After we gave the proper access rights to the logon account, the Entity Framework issue went away. Please remember that the access rights and the log on account to be used will be specific to your application.
I have an Entity Framework Project with several linked entities. Since it is utilized by multiple users at once I've set up a RowVersion-Field for entities which are likely to be edited by several users at once. Unfortunately I now get an OptimisticConecurrencyException every time I try to save a new entity, which is linked to an already existing entity.
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
The problem is now that this error doesn't really give any pointers as to where the error really lies. It could either be the underlying model that is modified in the meantime, there could be a validation error on the new model or something else.
The code I use to add the new entity is as follows:
using (ctx = new DbContext())
{
try
{
ctx.Samples.Add(model);
ctx.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
LogManager.HandleException(ex.InnerException);
}
}
model is the model i want to add to the database
Edit: As seen above i modified the code to ignore the update of an underlying model. Furthermore i have verified through:
ctx.Database.log = s => Debug.Write(s);
That only an insert statement is sent to the database and not an additional update statement.
INSERT [dbo].[Samples]([IDSample], [ModificationDate], [IDUser])
VALUES (#0, #1, #2)
SELECT [RowVersion]
FROM [dbo].[Samples]
WHERE ##ROWCOUNT > 0 AND [IDSample] = #0 AND [ModificationDate] = #1
I would understand the exception if i would update an entity and the rowversion column wouldn't match, but in this case it's a completely new entity. Is there a way to see if one of the properties is malformed?
Edit2:
Instead of just trimming the milliseconds i now used DateTime.Today instead of DateTime.Now which works. Seemingly there is some problem with datetime2(4) on ModificationDate. I already made sure that ModificationDate is truncated to 4 milliseconds so there should be no parse error.
Edit3:
After switching back to DateTime.Now and trimming the milliseconds it stopped working and the entities are not longer inserted into the database. Could this be caused by the fact that the sql server has problems matching the entities based on millisecond values. I executed the EF generated SQL as seen above with some fictional values and it went through although on some occasions the query didn't return a rowversion-value. In terms of the entity framework, the client would interpret this as a return value of 0 lines and therefore call an concurrency-exception. (It should also be of note that the ModificationDate together with the IDSample is the primary key of the entity.)
Edit4:
I'm now using DateTime.Today and then add the needed precision, which works for me. This can be flagged as solved. (Altough i would have expected that EF can take care of datetime-format-conversion by itself :/)
The question I have is where are/were you adding the DateTime? You are creating too many steps to hammer out this problem. Creating a datetime, modifying it, etc.
If you're entity is inheriting from a base class with mapped properties do your concurrency add/update in the DbContext override of SaveChanges().
Here's an example: (written without optimized syntax)
public abstract class EntityBase
{
public int Id {get; set;}
public DateTime CreationDate {get; set;}
public DateTime? ModifyDate {get; set;}
public string VersionHash {get; set;}
}
public static class EntityBaseExtensions
{
public static void MyBaseEntityMapping<T>(this EntityTypeConfiguration<T> configuration) where T : EntityBase
{
configuration.HasKey(x => x.Id);
configuration.Property(x => x.CreationDate)
.IsRequired();
configuration.Property(x => x.ModifyDate)
.IsOptional();
configuration.Property(x => x.VersionHash).IsConcurrencyToken();
}
}
public class MyEntity : EntityBase
{
public string MyProperty {get; set;}
}
public class MyEntityMapping : EntityTypeConfiguration<MyEntity>
{
public MyEntityMapping()
{
this.MyBaseEntityMapping();
Property(x=>x.MyProperty).IsRequired();
}
}
public class MyContext : DbContext
{
....
public override int SaveChanges()
{
this.ChangeTracker.DetectChanges(); //this forces EF to compare changes to originals including references and one to many relationships, I'm in the habit of doing this.
var context = ((IObjectContextAdapter)this).ObjectContext; //grab the underlying context
var ostateEntries = context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified | EntityState.Added); // grab the entity entries (add/remove, queried) in the current context
var stateEntries = ostateEntries.Where(x => x.IsRelationship == false && x.Entity is EntityBase); // don't care about relationships, but has to inherit from EntityBase
var time = DateTime.Now; //getting a date for our auditing dates
foreach (var entry in stateEntries)
{
var entity = entry.Entity as EntityBase;
if (entity != null) //redundant, but resharper still yells at you :)
{
if (entry.State == EntityState.Added) //could also look at Id field > 0, but this is safe enough
{
entity.CreationDate = time;
}
entity.ModifyDate = time;
entity.VersionHash = Guid.NewGuid().ToString().Replace("-", "").Substring(0, 10); //this an example of a simple random configuration of letters/numbers.. since the query on sql server is primarily using the primary key index, you can use whatever you want without worrying about query execution.. just don't query on the version itself!
}
}
return base.SaveChanges();
}
....
}
Any help or advice regarding this issue is greatly appreciated.
I am currently working on a project that requires a change in our data layer from a local MS SQL instance to a hosted Oracle solution.
Previously, we were using Entity Framework (Code-First) to build our data layer. I would like to take the same approach with the new data layer. We have several applications that use this library, so trying to keep the new data layer as close to the same (objects, names, etc.) as the original would be ideal. I know that Code-First is not officially supported by Oracle (a work in progress), but have read where others have had some success. Thus, for these reasons, I am attempting to do the same.
I have created my Oracle EF data layer to match as closely as I can to the original MS SQL EF data layer. The issue that I'm currently having is that when I run a query to retrieve the first or default entity from the data layer, I get the following exception:
Oracle.DataAccess.Client.OracleException: ORA-00942: table or view does not exist
If I use the exact same DbContext instance and instead run a sql query using the DbContext.Database.SqlQuery(sqlString), it works. The reason I mention this is because I've read the "table or view does not exist" error refers to a database permissions issue. That does not appear to be the case this time, since I'm using the exact same connection string in the same connection object. The only difference appears to be in using traditional sql statements versus the DbSet entities (& configurations).
My entities are relatively simple, flat objects.
public class HourlyPrice
{
public DateTime MarketDate { get; set; }
public int HourEnding { get; set; }
public string LocationId { get; set; }
public string LocationName { get; set; }
public decimal? Price { get; set; }
public int IsValid { get; set; }
public DateTime DateInserted { get; set; }
public DateTime? DateUpdated { get; set; }
}
public HourlyPriceConfiguration(string viewName)
{
ToTable(viewName);
HasKey(x => new { x.MarketDate, x.HourEnding, x.LocationName });
Property(x => x.Price).HasPrecision(13, 6);
HasEntitySetName("SourceHourlyPrices");
}
Inside my DbContext, I add the HourlyPriceConfiguration injecting the viewName ...
modelBuilder.Configurations.Add(new HourlyPriceConfiguration(this.viewName));
... and declare my IDbSet as ...
public IDbSet<HourlyPrice> SourceHourlyPrices { get; set; }
When running the code, this works ...
var sql = "select * from " + this.viewName;
var prices = db.Database.SqlQuery<HourlyPrice>(sql);
var price = prices.FirstOrDefault();
... but this ...
var price = db.SourceHourlyPrices.FirstOrDefault();
... produces the "table or view does not exist" error.
Is this just an issue with the Code-First approach, or am I missing something here? When debugging the application, I can see the viewName being passed to the configuration class is the same that is being passed to the sql statement used in SqlQuery. I've tried removing the HasEntitySetName() and changing the IDbSet to the standard HourlyPrices, but that didn't work, either.
Thanks again, in advance.
I would like to confirm that I had the same problem with a table name.
In Oracle if the name is not full UPPER CASE the table/view is not found.
Code First Automatic Migrations is limited to working with the dbo schema only.
https://community.oracle.com/thread/3622163
You can put it in beginning OnModelCreating method of your Context class as a workaround.
if (this.Database.Connection.GetType().Equals(typeof(Oracle.ManagedDataAccess.Client.OracleConnection)))
{
modelBuilder.HasDefaultSchema(new Oracle.ManagedDataAccess.Client.OracleConnectionStringBuilder(this.Database.Connection.ConnectionString).UserID);
}
The ORA-00942 exception is not a permission issue (depends on how you look at it of course); but it means that the table you are querying is not visible to your user.
You may try to explicitly set the name of your schema in your ToTable method call by using the ToTable(tableName, schemaName) implementation and see what happens.
Just wanted to add that I had the same problem after moving the DB to a different schema. I realised that it is critically to have the schema name in upper case when using ToTable(tableName, schemaName).
I have following repository. I have a mapping between LINQ 2 SQL generated classes and domain objects using a factory.
The following code will work; but I am seeing two potential issues
1) It is using a SELECT query before update statement.
2) It need to update all the columns (not only the changed column). This is because we don’t know what all columns got changed in the domain object.
How to overcome these shortcomings?
Note: There can be scenarios (like triggers) which gets executed based on specific column update. So I cannot update a column unnecessarily.
REFERENCE:
LINQ to SQL: Updating without Refresh when “UpdateCheck = Never”
http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=113917
CODE
namespace RepositoryLayer
{
public interface ILijosBankRepository
{
void SubmitChangesForEntity();
}
public class LijosSimpleBankRepository : ILijosBankRepository
{
private IBankAccountFactory bankFactory = new MySimpleBankAccountFactory();
public System.Data.Linq.DataContext Context
{
get;
set;
}
public virtual void SubmitChangesForEntity(DomainEntitiesForBank.IBankAccount iBankAcc)
{
//Does not get help from automated change tracking (due to mapping)
//Selecting the required entity
DBML_Project.BankAccount tableEntity = Context.GetTable<DBML_Project.BankAccount>().SingleOrDefault(p => p.BankAccountID == iBankAcc.BankAccountID);
if (tableEntity != null)
{
//Setting all the values to updates (except primary key)
tableEntity.Status = iBankAcc.AccountStatus;
//Type Checking
if (iBankAcc is DomainEntitiesForBank.FixedBankAccount)
{
tableEntity.AccountType = "Fixed";
}
if (iBankAcc is DomainEntitiesForBank.SavingsBankAccount)
{
tableEntity.AccountType = "Savings";
}
Context.SubmitChanges();
}
}
}
}
namespace DomainEntitiesForBank
{
public interface IBankAccount
{
int BankAccountID { get; set; }
double Balance { get; set; }
string AccountStatus { get; set; }
void FreezeAccount();
}
public class FixedBankAccount : IBankAccount
{
public int BankAccountID { get; set; }
public string AccountStatus { get; set; }
public double Balance { get; set; }
public void FreezeAccount()
{
AccountStatus = "Frozen";
}
}
}
If I understand your question, you are being passed an entity that you need to save to the database without knowing what the original values were, or which of the columns have actually changed.
If that is the case, then you have four options
You need to go back to the database to see the original values ie perform the select, as you code is doing. This allows you to set all your entity values and Linq2Sql will take care of which columns are actually changed. So if none of your columns are actually changed, then no update statement is triggered.
You need to avoid the select and just update the columns. You already know how to do (but for others see this question and answer). Since you don't know which columns have changed you have no option but set them all. This will produce an update statement even if no columns are actually changed and this can trigger any database triggers. Apart from disabling the triggers, about the only thing you can do here is make sure that the triggers are written to check the old and new columns values to avoid any further unnecessary updates.
You need to change your requirements/program so that you require both old and new entities values, so you can determine which columns have changed without going back to the database.
Don't use LINQ for your updates. LINQ stands for Language Integrated QUERY and it is (IMHO) brilliant at query, but I always looked on the updating/deleting features as an extra bonus, but not something which it was designed for. Also, if timing/performance is critical, then there is no way that LINQ will match properly hand-crafted SQL.
This isn't really a DDD question; from what I can tell you are asking:
Use linq to generate direct update without select
Where the accepted answer was no its not possible, but theres a higher voted answer that suggests you can attach an object to your context to initiate the change tracking of the data context.
Your second point about disabling triggers has been answered here and here. But as others have commented do you really need the triggers? Should you not be controlling these updates in code?
In general I think you're looking at premature optimization. You're using an ORM and as part of that you're trusting in L2S to make the database plumbing decisions for you. But remember where appropriate you can use stored procedures execute specific your SQL.