This is a follow-up to an earlier question I posted on EF4 entity keys with SQL Compact. SQL Compact doesn't allow server-generated identity keys, so I am left with creating my own keys as objects are added to the ObjectContext. My first choice would be an integer key, and the previous answer linked to a blog post that shows an extension method that uses the Max operator with a selector expression to find the next available key:
public static TResult NextId<TSource, TResult>(this ObjectSet<TSource> table, Expression<Func<TSource, TResult>> selector)
where TSource : class
{
TResult lastId = table.Any() ? table.Max(selector) : default(TResult);
if (lastId is int)
{
lastId = (TResult)(object)(((int)(object)lastId) + 1);
}
return lastId;
}
Here's my take on the extension method: It will work fine if the ObjectContext that I am working with has an unfiltered entity set. In that case, the ObjectContext will contain all rows from the data table, and I will get an accurate result. But if the entity set is the result of a query filter, the method will return the last entity key in the filtered entity set, which will not necessarily be the last key in the data table. So I think the extension method won't really work.
At this point, the obvious solution seems to be to simply use a GUID as the entity key. That way, I only need to call Guid.NewGuid() method to set the ID property before I add a new entity to my ObjectContext.
Here is my question: Is there a simple way of getting the last primary key in the data store from EF4 (without having to create a second ObjectContext for that purpose)? Any other reason not to take the easy way out and simply use a GUID? Thanks for your help.
I ended up going with a GUID.
The size/performance issues aren't
critical (or even noticeable) with SQL Compact, since
it is a local, single-user system.
It's not like the app will be
managing an airline reservation
system.
And at least at this point, there
seems to be no way around the "no
server-generated keys" limitation of
the SQL Compact/EF4 stack. If someone has a clever hack, I'm still open to it.
That doesn't mean I would take the same approach in SQL Server or SQL Express. I still have a definite preference for integer keys, and SQL Compact's bigger siblings allow them in conjunction with EF4.
Use a Guid. AutoIncrement is not supported on Compact Framework with Entity Framework.
Also, if you ever want to create a application which uses multiple data sources, int PK's are going to fall apart on you very, very quickly.
With Guid's, you can juse call Guid.NewGuid() to get a new key.
With int's, you have to hit the database to get a valid key.
If you store data in multiple databases, int PK's will cause conflicts.
What I've done for SQL CE before, and I assume we have a single application accessing the database, is to calculate the MAX value on startup and put it in a static variable. You can now hand out sequential values easily and you can make the code to generate them thread safe very easily.
One reason to avoid Guids would be size = memory and storage space consumption.
You could also query SQL Compact metadata like so:
SELECT AUTOINC_NEXT FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Categories' AND AUTOINC_NEXT IS NOT NULL
Related
I am encountering a recurring problem that just makes no sense, and hoping someone (in the Breeze team?) can shed some light.
The following model illustrates the entities in question.
As you can see, I'm adhering pretty strictly to Entity Framework conventions in my property names, and as a result, if I check in SQL the cascade on delete rules are set by EF code first when it creates the db.
Now, when I try to delete a BusUnit manually in SQL, the delete cascades correctly and the corresponding BusUnitDimensions are also deleted, as it should be. Likewise, if I delete a Dimension in SQL, the corresponding BusUnitDimensions are also deleted.
However, in my application, if I mark a BusUnit as setDeleted with Breeze and then try saveChanges, I get the following error.
The operation failed: The relationship could not be changed because one
or more of the foreign-key properties is non-nullable. When a change is
made to a relationship, the related foreign-key property is set to a null
value. If the foreign-key does not support null values, a new relationship
must be defined, the foreign-key property must be assigned another
non-null value, or the unrelated object must be deleted.
Strangely though, if I mark a Dimension for deletion and then save (within Breeze), the cascaded delete works correctly and both the Dimension and its corresponding BusUnitDimensions are deleted.
So, why the inconsistency? Why are the cascaded delete rules in SQL not being applied for BusUnits but yet they're working for Dimensions? I've read elsewhere that Breeze does not support cascaded deletes, but then why is my Dimensions case working?
EDIT:
I've removed my previous edits as they weren't relevant. The changes below follow on from Ward's answer...
My model now looks like this, and BusUnitDims now uses BusUnitId and DimId as a compound key, and I've added a bool, IsBud for the purposes of payload.
I haven't yet implemented deletes for BusUnits, but already if I try delete a Dim, I'm getting the same error message:
The operation failed: The relationship could not be changed because one
or more of the foreign-key properties is non-nullable. When a change is
made to a relationship, the related foreign-key property is set to a null
value. If the foreign-key does not support null values, a new relationship
must be defined, the foreign-key property must be assigned another
non-null value, or the unrelated object must be deleted.
I have noticed that cascaded deletes is no longer enabled, and in fact, to get EF to build the database I to add the following configuration:
modelBuilder.Entity<BusUnitDim>()
.HasRequired(bud => bud.BusUnit)
.WithMany(bu => bu.BusUnitDims)
.HasForeignKey(bud => bud.BusUnitId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<BusUnitDim>()
.HasRequired(bud => bud.Dim)
.WithMany(d => d.BusUnitDims)
.HasForeignKey(bud => bud.DimId)
.WillCascadeOnDelete(false);
So, with cascading now explicitly not in place, I can understand why the error occurs. Does that imply that in the controller, one has to specifically mark each map for deletion when deleting a parent Dim or BusUnit and before saveChanges is called, or is there some way to configure EF to take advantage of cascaded deletes as this would hugely simplify the code in my controller?
(PS: it gets even more complex, because BusUnitDims ends up having a further join table of its own, MetricBusUnitDims to accommodate yet another entity in the model and their relationships. This is why I'm trying to get the principles right early on)
EDIT: (A CONTROLLER SOLUTION FOR BUSUNITS)
So, the following approach works for BusUnits:
function deleteBusUnit(busUnitVm) { // note that you pass in the item viewmodel, not the entity
var busUnit = busUnitVm.busUnit;
var mapVms = busUnitVm.dimMapVms;
var dimHash = createBusUnitDimHash(busUnit);
mapVms.forEach(function (mapVm) {
var map = dimHash[mapVm.dim.id];
if (map) {
datacontext.markDeleted(map);
}
});
datacontext.markDeleted(busUnit);
save().then(function() { getDBoardConfig(); });
}
}
Is this the correct approach? if so, I'll still have to figure out the following:
How to approach Dims. These are different becuase the item viewmodel is defined for BusUnits.
How to approach the situation where there is a join tabel one level down, e.g. MetricBusUnitDIm.
EDIT: (A CONTROLLER SOLUTION FOR DIMS)
function deleteDim(dim) {
return bsDialog.deleteDialog(dim.name, true)
.then(function () {
vm.busUnitVms.forEach(function (busUnitVm) {
busUnitVm.busUnit.busUnitDims.forEach(function (bud) {
if (bud.dimId === dim.id) {
datacontext.markDeleted(bud);
}
});
});
datacontext.markDeleted(dim);
save().then(function () { getDboardConfig(); });
});
}
I believe your problems are traceable to the fact that your mapping table BusUnitDimension has its own primary key, Id, as opposed to the more typical approach in which the BusUnitId and DimensionId FK properties together comprise the compound primary key of BusUnitDimension.
Observe that OrderDetails in Northwind and the HeroPoweMap in the Breeze many-to-many example have compound keys.
Your choice creates complications.
First, it becomes possible to create multiple BusUnitDimension entities representing the same association between BusUnit and Dimension (i.e., they all have the same pair of FKs). The database may be able to prevent this (it's been a long time since I looked) but whether it does or doesn't, it won't prevent you from creating those duplicates in Breeze ... and maybe not in EF either.
Secondly, it opens you up to the problem you're currently facing. If those mapping entities are in the DbContext when you perform the delete, EF may (apparently does) try to null their FK properties as it sets either BusUnit or Dimension to the deleted state.
You can get around this, as has been suggested, by making both the BusUnitId and DimensionId FK properties nullable. But that is contrary to the semantics as a BusUnitDimension must link a real BusUnit to a real Dimension; they aren't optional. The practical consequence may be that you don't get cascade delete from the EF perspective if you do this (not sure if the DB will enforce that either). That means you'd have orphaned BusUnitDimension rows in your database with one or both FKs being null. I speculate because I'm not used to getting into this kind of trouble.
Another approach would be to set their FK values to zero (I think Breeze does this for you). Of course this implies the existence of BusUnit and Dimension table rows with Id == 0, if only during the delete operation.
Btw, you could actually have such "sentinel entities" in your DB.
You must make sure that these BusUnitDimension are in the deleted state or EF (and the DB) will either reject them (referential integrity constraint) or orphan them (you'll have BusUnitDimension rows in your database with one or both FKs being zero).
Alternatively, if you know that the DB will cascade delete them, you can simply remove them from the DbContext (remove from the EntityInfoMap in the EFContextProvider). But now you have to tell the Breeze client to get rid of them too if it happens to have them hanging around.
Enough Already!
These wandering thoughts should tell you that you've got yourself in a jam here with way too much bookkeeping ... and all because you gave BusUnitDimension its own Id primary key.
It gets a lot easier if you give BusUnitDimension the compound key, {BusUnitId, DimensionId}. You must also give it a payload property (anything will do) to prevent EF from hiding it in its "many-to-many" implementation because Breeze doesn't handle that. Adding any nonsense property will do the trick.
HTH
That has nothing to do with Breeze.. The originating message is from Entity Framework..
inside BusUnitDimension Model update BusUnitId property to:
public Nullable<int> BusUnitId { get; set; }
Notice the Nullable struct..
I have a legacy database with a particular table -- I will call it ItemTable -- that can have billions of rows of data. To overcome database restrictions, we have decided to split the table into "silos" whenever the number of rows reaches 100,000,000. So, ItemTable will exist, then a procedure will run in the middle of the night to check the number of rows. If numberOfRows is > 100,000,000 then silo1_ItemTable will be created. Any Items added to the database from now on will be added to silo1_ItemTable (until it grows to big, then silo2_ItemTable will exist...)
ItemTable and silo1_ItemTable can be mapped to the same Item entity because the table structures are identical, but I am not sure how to set this mapping up at runtime, or how to specify the table name for my queries. All inserts should be added to the latest siloX_ItemTable, and all Reads should be from a specified siloX_ItemTable.
I have a separate siloTracker table that will give me the table name to insert/read the data from, but I am not sure how I can use this with entity framework...
Thoughts?
You could try to use the Entity Inheritance to get this. So you have a base class which has all the fields mapped to ItemTable and then you have descendant classes that inherit from ItemTable entity and is mapped to the silo tables in the db. Every time you create a new silo you create a new entity mapped to that silo table.
[Table("ItemTable")]
public class Item
{
//All the fields in the table goes here
}
[Table("silo1_ItemTable")]
public class Silo1Item : Item
{
}
[Table("silo2_ItemTable")]
public class Silo2Item : Item
{
}
You can find more information on this here
Other option is to create a view that creates a union of all those table and map your entity to that view.
As mentioned in my comment, to solve this problem I am using the SQLQuery method that is exposed by DBSet. Since all my item tables have the exact same schema, I can use the SQLQuery to define my own query and I can pass in the name of the table to the query. Tested on my system and it is working well.
See this link for an explanation of running raw queries with entity framework:
EF raw query documentation
If anyone has a better way to solve my question, please leave a comment.
[UPDATE]
I agree that stored procedures are also a great option, but for some reason my management is very resistant to make any changes to our database. It is easier for me (and our customers) to put the sql in code and acknowledge the fact that there is raw sql. At least I can hide it from the other layers rather easily.
[/UPDATE]
Possible solution for this problem may be using context initialization with DbCompiledModel param:
var builder = new DbModelBuilder(DbModelBuilderVersion.V6_0);
builder.Configurations.Add(new EntityTypeConfiguration<EntityName>());
builder.Entity<EntityName>().ToTable("TableNameDefinedInRuntime");
var dynamicContext = new MyDbContext(builder.Build(context.Database.Connection).Compile());
For some reason in EF6 it fails on second table request, but mapping inside context looks correct on the moment of execution.
I have a query with EF which looks like this:
var x = _db.qMetaDataLookups.ToList();
if I execute, direct on the SQL server SELECT * FROM qMetaDataLookup, 2155 distinct rows are returned. After executing the above, x ALSO contains 2155 elements.
The problem is that the data is wrong. I'm not getting the same data back from the EF as I do from the SQL Query.
In particular, theres a particular element that exists on the SQL output, call it "WXYZ", which makes no appearance at all in the EF version of the query (against the exact same database).
Instead, what I find are numerous repeats. If I call x.Distinct() the list filters down from 2155 elements, to a mere 143.
I'm flummoxed. I have never seen my EF and SQL results differ on a query this simple. There must be a very simple [face-palm] explanation, but I'm missing it.
Thanks.
EDIT qMetaDataLookup (a view) are contains information about our database. In essence, its a listing of all tables and views, and each of their columns, with other information about the datatype, length, precision, scale, etc. The 'key' in this table ought to be the column that matches "tableName.columnName" but instead EF chose for it all the datatype properties. This is why the query fails to perform as desired.
Make sure the entity key is set correctly for qMetaDataLookup in the Entity Data Model. Sometimes the entity keys are messed up...
The issue might have been that your model was using a key with duplicate values where the Entity Framework was expecting unique values. This would happen if, for example, your data model used a composite primary key composed of foreign keys from other tables. It seems EF doesn't like composite primary keys very much, and so returned results from queries will generate what appear to be duplicated rows.
The fix seems to be to add a surrogate primary key column to your table which is guaranteed to be unique. If you still need to reference the foreign columns that's fine, so long as they aren't being used as a composite primary key for the table.
I can't claim any credit for the solution, but here's the link that helped me solve my issue:
http://jepsonsblog.blogspot.ca/2011/11/enitity-framework-duplicate-rows-in.html
I have 3 tables:
1. Invoice
InvoiceID int PRIMARY KEY
2. Order
OrderID int PRIMARY KEY
3. Transaction
TransactionID int PRIMARY KEY
Source int
Category string
On table "Transaction", Source (unfortunately) is behaving as a "polymorphic"(??) foreign key (there must be an actual term for that - sorry for my ignorance) that depending on the Category column it'll contain the ID of Invoice or Order.
However there's no actual foreign key.
Using EF 4.1 code first, anyone has any idea how I would create the proper associations?
Help is appreciated!
Thanks
Solution
Uh... Embarrassment is kicking in... I can just map it same way regardless of any actual DB foreign key.
I was having problems while trying to do that but basically wasn't related to this. I had computation properties that I didn't ask the context to ignore which was generating wrong queries.
You probably should create two nullable FKs instead of weak reference like that.
Uh... Embarrassment is kicking in... I can just map it same way regardless of any actual DB foreign key.
I was having problems while trying to do that but basically wasn't related to this. I had computation properties that I didn't ask the context to ignore which was generating wrong queries.
I just started to play with the entity framework, so I decided to connect it to my existing SQL Server CE database. I have a table with an IDENTITY(1, 1) primary key but when I tried to add an entity, I've got the above-mentioned error.
From MS Technet artice I learned that
SQL Server Compact does not support entities with server-generated keys or values when it is used with the Entity Framework.
When using the Entity Framework, an entity’s keys may be marked as server generated. This enables the database to generate a value for the key on insertion or entity creation. Additionally, zero or more properties of an entity may be marked as server-generated values. For more information, see the Store Generated Pattern topic in the Entity Framework documentation.
SQL Server Compact does not support entities with server-generated keys or values when it is used with the Entity Framework, although the Entity Framework allows you to define entity types with server-generated keys or values. Data manipulation operation on an entity that has server-generated values throws a "Not supported" exception.
So now I have a few questions:
Why would you mark key as server-generated if it is not supported and will throw an exception? It's hard to make sence from the quoted paragraph.
When I've tried to add StoreGeneratedPattern="Identity" to my entity's property, Studio complained that it is not allowed. What I'm doing wrong?
What is the best workaround for this limitation (including switching to another DB)? My limitations are zero-installation and using entity framework.
When I hit this limitation, I changed the type to uniqueidentifier
Use uniqueidentifier or generate a bigint/int key value manually is your best option.
Something like this perhaps ...
private static object lockObject = new object();
private static long nextID = -1;
public static long GetNextID()
{
lock (lockObject)
{
if (nextID == -1) nextID = DateTime.UtcNow.Ticks; else nextID++;
return nextID;
}
}
This assumes that you don't generate more than one record per tick during an application run (plus the time to stop and restart). This is a reasonable assumption I believe, but if you want a totally bullet proof (but more complex) solution, go read the highest ID from the database and increment from that.
SQL CE version 4.0 fixed this problem with its Entity Framework provider.
I just hit this issue too... mostlytech's answer is probably the best option, GUIDs are very easy to use and the risk of key collision is very low (although not inexistant).
Why would you mark key as server-generated if it is not supported and will throw an exception? It's hard to make sence from the quoted paragraph.
Because SQL Server (not Compact) supports it, and other third parties may support it too... Entity Framework is not only for SQL Server Compact ;)
In my case, all of my classes have the primary key named "ID"
I created an interface
public class IID
{
public Int32 ID { get; set; }
}
Then I create an extension method
public static Int32 GetNextID<T>(this ObjectSet<T> objects)
where T : class, IID
{
T entry = objects.OrderByDescending(u => u.ID).FirstOrDefault();
if (entry == default(T))
return 1;
return entry.ID + 1;
}
Then when I need a new ID, I just do this:
MyObject myobj = new MyObject();
myobj.ID = entities.MyTable.GetNextID();
the other option is to use SqlCeResultSet on the tables that have the identity column.
i have a primary key named ID with data type of INT32 and have Identity Column
Just do this
MyEntity Entity = new MyEntity();
String Command;
command = "Insert into Message(Created,Message,MsgType)values('12/1/2014','Hello World',5);
Entity.ExecuteStoreCommand(command);
--Exclude the primary key in the insert Statement
--Since the SQLCE do not support system generated keys
--Do not use LINQ because it supplies a default value to 0 for Primary keys that has a
data type of INT