I have been following the ASP.Net Movie Database Tutorial, and it was all going well.
I have just changed the model and added an attribute. Call me curious, but I didn't want to follow the guide of just drop the database - I wanted to see if I can modify.
The correct error came up about a mismatch - which was expected.
I added the ratings column to the database, and everything worked.
Next, I deleted the ratings column as I wanted to follow the tutorial and learn about DropCreateDatabaseIfModelChanges - however, I just get the error Invalid column name 'Ratings'.
In all this time, the ModelHash entry has not changed, and I have no idea how it know there is a difference between now or before.
So - 1) Have I screwed something up?
2) How can I fix?
3) How did it know before that something has changed, but not now when the hash hasn't changed?
4) Is there any additional advice you can give?
I think you description must be incorrect because correct behavior will throw the exception despite of manually added Rating column.
This behavior works as follows:
When database is created new table called EdmMetadata is added.
EdmMetadata contains two columns - Id and ModelHash.
Part of database creation is storing single row with hash of the current model.
If database was not created by the current context the context executes a query to retrieve stored model hash before the first operation is executed. The retrieved has is compared with context's current hash.
If hash is not the same and database recreation is not allowed by database initializer the exception is thrown.
If hash is the same, required db operation is executed.
Adding manually Rating column will not change stored hash but hash of the model will be different.
This behavior can be completely removed by removing IncludeMetadataConvention convention:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove(modelBuilder.Conventions
.OfType<IncludeMetadataConvention>().ToArray());
}
Related
In EF Core you can "reload" an Entity from the data store to pickup any changes.
An example of why you might need to do that is if you need to resolve a DB concurrency exception on SaveChanges.
This does work ok for one record...
EntityEntry<T> entityEntry = GetEntity(123);
entityEntry.Reload();
The only problem is, the Reload() executes a SQL statement per entityEntry.
So, if you want to refresh a set of entityEntry, you get a SQL statement per entry.
Whereas, the normal context.Set<T>().Load(); executes one SQL statement that retrieves all of the rows.
With a small number of entityEntry, the performance hit is negligable; but anytime I see a RBAR design approach, it raises a red flag for me.
Question: other than a RBAR loop
foreach (var e in context.ChangeTracker.Entries<T>)
{
e.Reload();
}
is there a way to re-execute the context.Set<T>().Load(); to reload the entries as a set?
Note: I am using the
context.Entry(e).State = EntityState.Detached
approach now; checking to see if there is something better that I have missed.
UPDATE 1
How I am testing
Read data from database
Add record to database
Delete record from database
Change record in database
"reload" data
the result I am looking to get is
entity for changed record is updated
entity for deleted record is removed
"optionally" entity for new record is added
the Add is optional because that is not the designed behaviour of "reload", which is to update existing entity.
I need to read the result from a stored procedure called from EF Core that includes things intended for spreadsheet formatting purposes such as rows with only null values. My code is mostly working, but there are two columns that cause the query to fail when I include them.
Here's my model class (only a few props included for brevity):
public class StoredProcRecord
{
[Column("Prod Line")]
public string ProductLine { get; set; }
[Column("Current Sales")]
public decimal? CurrentSales { get; set; }
[Column("Current Margin $")]
public decimal? CurrentMargin { get; set; }
}
Here's a snippet of part of the output if I run the stored procedure from SQL Server Management Studio:
Here's how I registered the type as an entity model with EF Core in the db context:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Query<StoredProcRecord>();
}
Here's how I'm calling the stored procedure:
return _context.Query<StoredProcRecord>().FromSql("EXECUTE my_stored_proc").ToList();
In this example, EF Core always throws an exception when trying to call FromSql() with a message stating:
The required column 'Current Margin $' was not present in the results
of a 'FromSql' operation.
According to the EF Core docs, as long as a column is nullable, then it is not required. Yet, I receive that exception that claims "Current Margin $" is required. It also only happens with the CurrentMargin property, not the CurrentSales property, which uses the same type. If I remove CurrentMargin, the whole thing works fine and I'm just missing that one property.
I have tried using [Column(Order = 2)] instead of a named column, thinking maybe the dollar sign in the column name had something to do with it, but that results in:
The required column 'CurrentMargin' was not present in the results of
a 'FromSql' operation.
Including both order and name results in the same type of error.
Worth noting, this is not a stored procedure that I have the option to make updates to.
What is different about the CurrentMargin property compared to CurrentSales that causes it to fail like this? Is there some special way I need to handle the dollar sign in the column name? Am I missing something else completely?
Edit:
Here's a snippet from the very end of the stored procedure where the resulting table is output. I'm including this to show that "Current Margin $" is indeed one of the column names. I actually copied/pasted right from the stored procedure just to make sure I hadn't mis-typed the column name.
BEGIN
-- bulk of stored procedure that builds #tmp_tbl omitted for length
SELECT
'Prod Line' = pl_key,
'Current Sales' = curr_sales,
'Current Margin $' = curr_margin
FROM #tmp_tbl
END
The embarrassing answer to this question is make sure you read your code carefully, especially things like string values.
In this case, I have two stored procedures that will eventually get used by my app, bv_xls_profit_sum_v2 and bv_xls_prod_sum_v2. I was implementing usage of the results of the first stored procedure, but I accidentally provided the name of the second stored procedure to EF. The columns from those two do not match, so the error I was receiving about missing required column wasn't about whether or not the column was nullable, but was actually a case where the column was indeed missing from the result set completely. This wasn't obvious when testing through SQL Server Management Studio because I didn't realize I was running a different stored procedure.
Bonus "Answer":
Prior to figuring out my stupid mistake, I did figure out a workaround that I'll leave here just in case it proves useful for anyone. My app has its own SQL Server instance, but also queries against another SQL Server instance that is not controlled by the app (that's where the stored procedures in question live). To work around the issue I was struggling with, I connected the other SQL Server instance to mine as a linked server. I then duplicated the stored procedure I wanted to call, updated it to have it query the linked server, eliminate unwanted null rows, and return using column names to match my model class. This does work and allows me to manage the stored procedure, though it does likely add additional latency to getting a result set back from querying the modified stored procedure.
This is a bit of a puzzle I'm trying to figure out.
I am working on a system where we have a number of company records saved in the database. Some of these records are duplicates and are no longer wanted/required.
However, several external systems are still mapping to these invalid records. If we were to delete them entirely it would cause errors to the systems still wanting to get the detail of that company.
The ideal workflow I would like would be;
The external system looks up Company ID X.
The current system has a table which has a record of all the remapped records, so when the request comes in, the table specifies to redirect Company ID X to Company ID Y.
There are a number of endpoints that could be altered one-by-one to do this - but it would be time-consuming, resulting in lots of repetition too.
My question is, using Entity Framework and .Net - is there a smart way of achieving this workflow?
My initial thoughts were to do something with the constructor for the company object, which repopulates the object from EF if a 'redirect' exists, but I don't know if this will play nice with navigation properties.
Would anyone have an idea?
Thanks very much.
You can create a column with foreign key for the same table to express the single unique valid company.
For example, you can add DuplicateOf column:
ALTER TABLE [Company]
ADD COLUMN [DuplicateOf] bigint NULL,
FOREIGN KEY [DuplicateOf] REFERENCES [Company] ([Id]);
and express this relation in your code:
public class Company
{
// ...
public Company DuplicateOf { get; set; }
// May be useful, hides check for duplicate logic:
public bool IsDuplicate => DuplicateOf != null;
// May be useful as well,
// returns the non-duplicate uniue company, not a duplicate, either linked or current:
public Company EffectiveCompany => DuplicateOf ?? this;
}
You will have to address EffectiveCompany when you want to work with non-duplicate and maintain this column to always point to the correct record. It will also result into additional query, if eager-loaded.
Another idea is to have a stored procedure GetCompany(bigint id) which will return the effective record - if DuplicateOf exists, or record itself otherwise. It will be good for your external systems and will let you hide all this stuff behind abstraction layer of stored procedure. If you decide to change it in future, then you can easily update it without breaking external systems.
However, for you it isn't always convenient to work with stored procedures with EF.
These are just ideas and not the best solutions, anyway.
In my opinion, the best solution would be to get rid of duplicates, update data everywhere and forget forever about this mess of duplicated data.
I have a business object type representing customers which, when its .Save() method is called, attempts to retrieve (using LINQ) a matching entity from the database based on the object's ID property. If it does not find a matching entity, it creates a new one; if it does find a matching entity it updates that entity's properties and calls [my datacontext].SubmitChanges().
That last part is the problem. Much of the data for a user is stored in an XML field in the database, named content_xml. There is a bug in the code which is failing to retrieve two of those data items ("coordinates" and "sales_groups") when constructing the business object, and so when the .Save() method goes to update the entity, the XML it's sending is missing those elements.
For some reason this is throwing a ChangeConflictException, stating that "1 of 12" updates failed. In order to identify what was causing the problem, I used the code from http://msdn.microsoft.com/en-us/library/Bb386985%28v=vs.100%29.aspx to extract information about the change conflict.
From that, I see that [my datacontext].ChangeConflicts contains a single ObjectChangeConflict, which itself contains a single MemberChangeConflict representing the XML field.
The part I do not understand is that when checking the values of currVal, origVal and databaseVal, I see that XML data held in currVal is what I would expect based on the bug (it's missing the two elements), and the XML for origVal and databaseVal are identical. That shouldn't cause a conflict, should it?
Even more strange is that when I manually correct the bug by providing the correct (according to the database) values for the missing elements, just before the .SubmitChanges() call, it still causes a conflict, even though the XML from all three (currVal, origVal and databaseVal) all now look identical.
Can anyone suggest what might be causing the conflict?
Edit:
OK, this is a bit of a surprise, but even if I never set the content_xml property value of the retrieved entity before submitting changes, I still get a conflict on the XML field.
I would guess that the change conflict stems from L2S comparing old vs new value in an incorrect way for your xml field.
A possible workaround for this is to add a timestamp or rowversion column to the table and updating the L2S model. If a table contain rowversion/timestamp, only that column will be used for detecting change conflicts...
Imagine an object with a field that can't have a duplicate value in the database. My first instinct was to create a unique attribute that I could apply as a data annotation to a property. This unique attribute would hit the database and check if the value already exists. This would work when executing a create method, but would fail on an update. On an update, I would get a duplicate value error for every unique field of my entity whose value I don't want to change. What would be a good way, or an established practice, to accomplish this on ASP.NET MVC 2 in a way that fits nicely with the ModelState? Passing the id of my object to the attribute validator could work by checking if the duplicate value that is found is of the same entity that I am updating but I don't know how to get that data from inside of the validator.
Please forgive me if this is a stupid question or if it is phrased incoherently. It's almost 3 in the morning and I've been coding since the morning of yesterday.
For this kind of validation, I would let the database do what it already does so well. Make sure your database has the unique constraint and let it report back an error if you violate it. You can then add the error to the model errors (with a nice friendly bit of text, rather than just plonking the SQL error).
If you are determined to perform a check yourself, you can get around the UPDATE problem by excluding the current record...
SELECT COUNT(*)
FROM myTable
WHERE myTable.UniqueValue = 'ShouldBeUnique'
AND myTable.Id <> 5
In this example, you use the id of the record you are updating to avoid checking it, which means you just check other records to see if they contain the unique value.