Updating entity without having the know primary key - entity-framework

Given the following code, how can I add an element to one of the properties of an entity without knowing its Id and retrieving it from the database?
public async Task BookInPersonVisitAsync(Guid propertyId, DateTime dateTime, CancellationToken token)
{
var entity = new OnBoardingProcessEntity{ ExternalId = propertyId };
DbContext.OnBoardingProcesses.Attach(entity);
entity.OnBoardingProcessVisits.Add(new OnBoardingProcessVisitEntity
{
DateTime = dateTime,
Occurred = false
});
await DbContext.SaveChangesAsync(token);
}
ExternalId is just a guid we use for external reference. This doesnt work cause it does not have the id set, but without hitting the database we cant have it.

With entity framework if you have to reference an entity (referencedEntity) from another entity (entity) you have to know referencedEntity.
Otherwise you can add just add the entity setting the referencedEntity to null.
To know the referencedEntity or you know the Id or you have to retrieve it in some ways (from the database).
In SQL (DML) if (and only if) ExternalId is a candidate key noy nullable you can insert the OnBoardingProcessVisit record with a single roundtrip but the insert statement will contain an inner query.
OnBoardingProcessVisit.OnBoardingProcess_Id = (
SELECT
Id
FROM
OnBoardingProcess
WHERE
ExternalId = #propertyId)
EDIT
No way to generate that query with EF. You can have a look to external components (free and not free, for example EntityFramework Extended but in this case I think that doesn't help).
In this case I probably would try to use standard entity framework features (so 1 roundtrip to retrieve the OnBoardingProcess from the ExternalId).
Then, if the roundtrip is too slow, run the SQL query directly on the database.
About performances (and database consistency) add a unique index on OnBoardingProcess.ExternalId (in every case).
Another suggestion if you decide for the roundtrip.
In your code, the entity will be a proxy. If you don't disable lazy load, using your code you will do one more roundtrip when you will access to property
entity.OnBoardingProcessVisits (in the statement entity.OnBoardingProcessVisits.Add).
So, in this case, disable lazy load or do the same using a different way.
The different way in your case is something like
var onBoardingProcessVisitEntity new OnBoardingProcessVisitEntity
{
DateTime = dateTime,
Occurred = false,
OnBoardingProcess = entity
});
DbContext.OnBoardingProcessVisits.Add(onBoardingProcessVisitEntity);
await DbContext.SaveChangesAsync(token);

Related

Retrieving some columns from database with Breeze.js, and still be able to update database

I am new to Breeze.js, but really enjoy it so far. I ran into an issue with updating a database with Breeze.js, when selecting only portion of columns of a model.
When I ran this statement:
$scope.emFac.entityQuery.from('Company');
the company entity matches my EF entity, retrieves all columns, creates entityAspect, and all is working fine when updating database:
However, when I retrieve only portion of corresponding Model's columns, Breeze.js returns anonymous object with specified properties (retrieving data works, but not updating does not), without the entityAspect, which is being used for tracking changes.
Here is the code with select statement:
$scope.emFac.entityQuery.from('Company').select('companyId, displayName');
Is there a way to retrieve only some columns of EF Model columns, and still track changes with Breeze.js, needed for database updates?
As you've discovered, Breeze treats the incoming data as plain objects instead of entities when you use select.
Your choices are:
On the server, Create a CustomerLite or similar object, and have a server endpoint that returns those without the need for select; OR
On the client, get the results from the query and create entities from each object, with status Unchanged
Example of #2:
var entities = [];
em.executeQuery(customerProjectionQuery).then(queryResult => {
queryResult.results.forEach(obj => {
// obj contains values to initialize entity
var entity = em.createEntity(Customer.prototype.entityType, obj, EntityState.Unchanged);
entities.push(entity);
});
})
Either way, you will need to ensure that your saveChanges endpoint on the server can handle saving the truncated Customer objects without wiping out the other fields.

How can I get the SqlDbType or DbType from IModel in EntityFramework Core 2?

My use case is:
I have a graph of objects from many entities in Entity Framework Core 2 (EFC 2). In EFC 2, the SaveChanges Operation is very slow. The reason appears to be the limit in number of parameters that Sql Server can receieve per query. As Sql Server receive 2100 parameters per query, saving hundreds of thousands of registers must cause many roundtrips, with much latency implied. See issue 2484 for more information. My current solution is to generate a SqlCommand with a query with Table Valued Parameters (TVP). The plan is to use only one query with a TVP for each table and operation (insert, update and delete), using only one roundtrip for save all the changes. EFC cannot do that actually.
In theory, i'm almost finish this, but i have a problem. To use TVP, i must get the SqlDbType for each column from my Table Type. The Table Types are generated using the metadata in the IModel from EFC. But, i cant get the SqlDbType. Simplified, I tried with:
var typeMapper = new SqlServerTypeMapper(new RelationalTypeMapperDependencies());
var entityType = context.Model.GetEntityTypes().First();
var prop = entityType.GetProperties().First();
var mapping = typeMapper.GetMapping(prop);
var dbType = mapping.DbType;
Having dbType, the plan is get the SqlDbType from dbType using a Dictionary. The problem is dbType is getting null.
I'm searched in the api, and i can get the way to extract a SqlType from IModel. Is this possible?

EF 6, adding entity to DbContext creates duplicate entity

I have a web application (MVC 5, EntityFramework 6). It's connected to an SQL database via a DbContext. I'm having an issue where adding a new entity object creates a duplicate entry in the entity set (but not the DB) and I'm not sure how to stop this from happening.
Controller, whose method is called via an ajax request:
public class CustomerController : Controller
{
MyDBEntities db = new MyDBEntities(); //DbContext
public ActionResult SaveStuff(string customerId, string stuff)
{
Customer customer = db.Single(c => c.ID.Equals(customerId));
Stuff stuff = new Stuff(stuff, customer);
db.Stuffs.Add(stuff);
db.SaveChanges();
return PartialView("MyControl", customer);
}
}
There is a 1-to-many association between Customer and Stuff, and there is a "Stuffs" navigation property in Customer.
Stuff includes fields that are int, string, and DateTime.
The controller method returns a PartialView which is used by JavaScript to refresh the contents of a control.
The "MyControl" control does this:
var stuffs = Model.Stuffs.OrderByDescending(...);
When the control is rendered in this situation, Model.Stuffs contains a duplicate entry. There's an entry with a name of Stuff (probably the new object created in the control method) as well as well as an entry with a name of System.Data.Entity.DynamicProxies.Stuff_<uuid> which is the same exact data (I imagine read from the DB).
This is only a problem when I'm writing into and then reading from an entity set within the same web request. Other/future web requests that cause a read are fine. How can I make this work correctly?
This is happening because the DateTime object is losing precision when it is written into the SQL database (see: SQL Server DateTime vs .NET DateTime). When read back from the DB, it has a different value and therefore does not overwrite the existing "stuff" object that still exists locally in db.Stuffs.
A simple solution is to change the DateTime's setter for Stuff to private and add your own pseudo-setter function that has the rounding built into it:
public void SetTimestamp(DateTime timestamp)
{
//Precision in SQL is lower than in .NET, so just round to tenth seconds
this.Updated = timestamp.AddTicks(- (timestamp.Ticks % (TimeSpan.TicksPerSecond / 10)));
}
Using DateTime2 in the SQL database (Server 2008+) is also an option should you need to maintain that level of precision.

GUID or int entity key with SQL Compact/EF4?

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

Server-generated keys and server-generated values are not supported by SQL Server Compact

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