EF 4.0 POCO magic doesn't work-- No changes detected - entity-framework

I can't seem to update my database from disconnected poco objects. In this example, I fetch an ApplicationUser object, update varchar(100) field SSOID, but no changes take effect. Even when I refetch the object in the save method, nothing gets sent to the db.
If I try to call ApplyCurrentValues, it throws
An object with a key that matches the key of the supplied object could not be found in the ObjectStateManager. Verify that the key values of the supplied object match the key values of the object to which changes must be applied.
public void Save(ApplicationUser au)
{
ApplicationUser original = context.ApplicationUsers.FirstorDefault(a => a.UserID == au.UserID);
context.ContextOptions.ProxyCreationEnabled = false; //changing this has no effect
if(original == null)
{
context.AddObject("ApplicationUsers",au); //this works!
}
else
{
///let's manually set some properties on the object I just fetched.
original.SSOID = au.SSOID;
ObjectStateEntry entry = null;
context.DetectChanges();
context.ObjectStateManager.TryGetObjectStateEntry(original.EntityKey, out entry);
//entry is still null!
context.ApplicationUsers.ApplyCurrentValues(original); //BOOM!!
}
context.SaveChanges();
return;
}
I tried everything I can think of, even making ApplicationUser implement System.Data.Objects.DataClasses.IEntityWithKey, which supposedly isn't necessary.
Here's the mapping:
<EntitySetMapping Name="ApplicationUsers">
<EntityTypeMapping TypeName="MyCompanyName.ApplicationUser">
<MappingFragment StoreEntitySet="ApplicationUser">
<ScalarProperty Name="UserID" ColumnName="UserID" />
<ScalarProperty Name="SystemID" ColumnName="SystemID" />
<ScalarProperty Name="Username" ColumnName="Username" />
<!--snip-->
<ScalarProperty Name="CreatedOn" ColumnName="CreatedOn" />
<ScalarProperty Name="CreatedBy" ColumnName="CreatedBy" />
<ScalarProperty Name="SSOID" ColumnName="SSOID" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>

Are you using the straight/simple POCO T4 template, or the self-tracking entities template??
The straight POCO have no support for any change tracking whatsoever - if you want that, you need the self-tracking entities.
See resources:
Using Self Tracking Entities to retrieve and update
Working with Self-Tracking Entities
Update: I think you are quite close to the right way of doing things here. You re-load the original state of the object (I would probably add a check on a timestamp or something to make sure the object in store hasn't been changed in the meantime).
Once you've done that, I believe you just need to detect yourself what changes have happened / where differences exist between au and original; update the original accordingly, and then just simply call context.SaveChanges().
As far as I understand it, the .DetectChanges() can't work, since you're using straight POCO classes without any change tracking, e.g. your au object doesn't have any way of knowing what's been changed - you basically need to do a property-by-property comparison yourself.

Related

Two entities mapped to the same rows

I'm getting the following error on my database first model in Entity Framework:
Error 3032: Problem in mapping fragments starting at lines 3434,
4312:EntityTypes Model.Docent, Model.Student are being mapped to the
same rows in table Attendee. Mapping conditions can be used to
distinguish the rows that these types are mapped to.
While I already added conditions to these models:
<EntityTypeMapping TypeName="IsTypeOf(Model.Student)">
<MappingFragment StoreEntitySet="Attendee">
<ScalarProperty Name="Id" ColumnName="atnId" />
<Condition ColumnName="atnTypeId" Value="1" />
</MappingFragment>
</EntityTypeMapping>
And
<EntityTypeMapping TypeName="IsTypeOf(Model.Docent)">
<MappingFragment StoreEntitySet="Attendee">
<ScalarProperty Name="AvailabilityApprovedByType" ColumnName="atnAvailabilityApprovedByAttId" />
<ScalarProperty Name="Id" ColumnName="atnId" />
<Condition ColumnName="atnTypeId" Value="2" />
</MappingFragment>
</EntityTypeMapping>
Their is a more complex hierarchy, possibly that's the problem. But I'm unsure how to proceed. This a the hierarchy:
Attendee (Abstract)
-> Facility (Type = 3)
-> AttendeeCollection (Abstract)
-> Team (Type = 4)
-> Group (Type = 5)
-> Person (Abstract)
-> Student (Type = 1)
-> Docent (Type = 2)
Well I figured out the problem. The Person entity had mapped scalar properties and associations. The associations where the problem. Because they could be of two types. I could not write in a condition for them because they could map to two properties. So I removed the scalar properties and the table mapping for the Person class altogether.
After that I implemented private versions of these scalar properties on the Docent en Student class. And exposed them via a partial implementation. Where I added them as abstract to the Person class.
I hope this is clear and helps somebody else. Possibly somebody else can write it down more legible.

CodeFluent tries to update a non-nullable foreign key of type bigint with NULL when deleting related entity

In my model I'm using bigint (ulong) as the type for entity keys.
The value 0 must be used for empty keys.
Columns for foreign keys must not be nullable, because in my methods I only want to check for the value 0 and not for null.
Everything works fine, except for the deletion of related entities that are referenced by other entities.
Here is my model:
<cf:entity name="Customer" cfom:bindingList="false">
<!--persistenceIdentity is true, because the corresponding column for this property must be auto incremented by the database.-->
<cf:property name="Id" typeName="ulong" key="true" persistenceIdentity="true" cfps:hint="CLUSTERED" />
<cf:property name="Name" typeName="string" />
</cf:entity>
<cf:entity name="Order" cfom:bindingList="false">
<!--persistenceIdentity is true, because the corresponding column for this property must be auto incremented by the database.-->
<cf:property name="Id" typeName="ulong" key="true" persistenceIdentity="true" cfps:hint="CLUSTERED" />
<!--persistenceNullable is false, because the column for the foreign key must not be nullable.-->
<cf:property name="Customer" typeName="{0}.Customer" persistenceNullable="false" />
</cf:entity>
Here is my code:
Customer customer = new Customer();
customer.Save();
Order order = new Order();
order.Customer = customer;
order.Save();
customer.Delete();
The last statement gives the following error:
Cannot insert the value NULL into column 'Order_Customer_Id', table 'CodeFluentTest.dbo.Order'; column does not allow nulls. UPDATE fails.
This is because the Customer_Delete stored procedure contains the following update statement:
UPDATE [Order] SET [Order].[Order_Customer_Id] = NULL
WHERE ([Order].[Order_Customer_Id] = #Customer_Id)
Of course this will not work, because the Order_Customer_Id column is not nullable.
How can I instruct CodeFluent to put the value 0 instead of NULL into the Order_Customer_Id column?
CodeFluent does not really support object keys that are not nullable, because the implied semantics would be somehow strange. When you delete an object, from an OO perspective, the object is now null, it's identifier does not exist any more, it's not set to a specific 0 or other value. You will probably run into other problems with tweaking the model like this.
That being said, one way to change this behavior is to use the "persistenceUnlink" attribute on the property involved, directly in the XML file. Unfortunately the graphical modeler does not support this (ancient) attribute and will override it everytime you modify the model and save it back using the GUI.
So, what you can do is use a custom aspect to automatically apply this attribute on properties where you want it. Here is a sample code for such an aspect (note the aspect runs on startup because it's really based on XML, not on the in-memory model contrary to most aspects):
<cf:project xmlns:cf="http://www.softfluent.com/codefluent/2005/1" defaultNamespace="unlink">
<cf:pattern name="Unlink Aspect" namespaceUri="http://www.example.com/unlink" preferredPrefix="ul" step="Start">
<cf:message class="_doc">
Sample aspect that removes auto unlink in delete procedures
</cf:message>
<cf:descriptor name="unlink" targets="Property" defaultValue="true" displayName="Unlink" typeName="boolean" description="Determines if this property will be unlinked during delete" category="Unlink Aspect" />
</cf:pattern>
<?code #namespace name="System" ?>
<?code #namespace name="System.Xml" ?>
<?code #namespace name="CodeFluent.Model" ?>
<?code
// use a special utility method to get all elements
// with the given attribute in a given namespace URI
var properties = Project.Package.RootModelPart.SelectElements("unlink", "http://www.example.com/unlink", false);
foreach(var property in properties)
{
// here we set a special attribute not supported by the GUI designer in Visual Studio
property.SetAttribute("persistenceUnlink", "false");
}
?>
</cf:project>
What you must do is:
save this code as a file, for example "unlink.xml" somewhere around your project files.
in the graphical modeler, right-click on the "Aspects" node of the CodeFluent project, and choose "Add existing aspect", browse to your unlink.xml file (it should display the aspect metadata), and press ok.
go back to the design surface in the graphical modeler, click on the property you want to remove persistence link, go to the Visual Studio property grid, choose the blue "aspects and producers properties" tab, and set "Unlink" to False that should now be displayed (default is true).
rebuild, and the stored procedure code should not contain links for this relation any more.

codefluent custom stored procedure

I have a custom stored procedure with in parameters that return fields of different tables how I can map this custom stored to an entity? I only want to use like a read only values for a report I don't want to save or something like that I try to add the extra fields to the most similar entity but when I execute the method in code the extra fields are null
Solution 1: Using a view
A view allows to aggregate data from different entities.
<Article>
<Id />
<Name />
<Lines typeName="LineCollection" />
<cf:method name="LoadArticlesByCommand" body="load(string commandName) from ArticleByCommand where CommandName = #commandName" />
<cf:view name="ArticleByCommand" autoLightweight="true">
<ArticleName expression="Name"/>
<ArticleQty expression="Lines.Quantity" />
<CommandName expression="Lines.Command.Name" />
</cf:view>
</Article>
<Command>
<Id />
<Name />
<Lines typeName="LineCollection" />
</Command>
<Line setType="List">
<Article typeName="Article" key="true" />
<Command typeName="Command" key="true" />
<Quantity typeName="int" />
</Line>
http://blog.codefluententities.com/2014/04/22/views-auto-lightweight-and-the-modeler/
https://www.softfluent.com/documentation/Views_PersistentViews.html
Solution 2: Using a lightweight entity
Instead of creating a view, you can can create a lightweight entity that contains only the properties used by the stored procedure.
<cf:entity name="Person" lightweight="true">
<cf:property name="FirstName" typeName="string" />
<cf:property name="lastName" typeName="string" />
<cf:method name="ComputeBalance"
body="load () raw"
rawBody="SELECT 'John' AS FirstName, 'Doe' AS LastName" />
</cf:entity>
Solution 3: Custom mapping
For more specific values or types, a custom method can be provided to map the database values to .NET types. This custom method will be called with a DataReader as parameter, meaning that a developer could do whatever he wants.
<cf:entity name="Sample">
<cf:method name="LoadPair" body="raw" rawBody="SELECT 1234,5678"
returnTypeName="CodeFluent.Runtime.Utilities.Pair<System.Int32,System.Int32>"
cfom:methodName="On{0}" />
<cf:snippet>
private static CodeFluent.Runtime.Utilities.Pair<int,int> OnLoadPair(System.Data.IDataReader reader)
{
return new Pair<int, int>(reader.GetInt32(0), reader.GetInt32(1));
}
</cf:snippet>
</cf:entity>
You can also use OnAfterReadRecord or OnBeforeReadRecord rules
If it is not essential that you map the results of the custom stored procedure to an entity than another option is to use the built in support for DataSets.
http://blog.codefluententities.com/2011/06/22/dataset-support-in-codefluent-entities/
<cf:method name="LoadAllCities" body="raw" returnTypeName="System.Data.DataSet">
SELECT $Address::City$ FROM $Address$
</cf:method>
.
DataSet ds = Address.LoadAllCities();
foreach (DataTable table in ds.Tables)
{
foreach (DataRow row in table.Rows)
{
Console.WriteLine("City: " + row[0]);
}
}
Upon re-reading you're question I am providing another answer.
In response to the part where you said "I try to add the extra fields to the most similar entity but when I execute the method in code the extra fields are null". The following steps should be able to solve that problem.
Execute one of the automatically created stored procedure in SQL Management Studio.
Execute the stored procedure you manually created.
Verify that the fieldnames returned by both stored procedures match.
I think the above will solve your immediate problem but I still don't like the solution. The reason is that you said you picked the most similar entity. I think that is going to cause problems in the future especially if the stored procedure is not being mapped to all of the entities properties.
I would recommend either lightweight entity, view or DataSet.

Entity Framework designer custom property does not appear in DDL generation

I have added a custom property to the Properties dialog of the Entity Framework 5 designer through
http://msdn.microsoft.com/en-us/library/microsoft.data.entity.design.extensibility.ientitydesignerextendedproperty(v=vs.103).aspx
This works well, the property appears in the Properties dialog and it is saved in the EDMX file.
Now I'd like to use that property in the DDL generation process. I have edited the T4 template file SSDLToSQL10.tt (found at C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\Extensions\Microsoft\Entity Framework Tools\DBGen).
However, the custom property doesn't seem to appear anywhere in the metadata tree. The website (in German)
http://www.databinding.net/en/blog/post/2010/12/13/entity-framework-4-erweiterte-eigenschaften-in-einer-t4-vorlage-verwenden.html
tells me that the extended property should appear in the EntityType.MetadataProperties collection, but this collection contains only the following members:
KeyMembers Members Name NamespaceName Abstract BaseType DataSpace MetadataProperties
None of those is my custom property.
Am I missing something? How can I access the IEntityDesignerExtendedProperty's value in the T4 code generation template?
EDIT: Here is the EDMX part with the custom property:
<edmx:ConceptualModels>
<Schema ...>
....
<EntityType Name="Entity1">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Type="Guid" Name="Id" Nullable="false" annotation:StoreGeneratedPattern="None" />
<Property Type="String" Name="Name" Nullable="false" />
<a:MyNewProperty xmlns:a="http://schemas.tempuri.com/MyNewProperty">True</a:MyNewProperty>
</EntityType>
I guess I have to map that custom property from CSDL to SSDL somehow.
You added the property to the CSDL (conceptual layer) while the DDL is created using the SSDL (store layer). You should be able to access the conceptual model in the SSDLToSQL10.tt but I don't think it is really what you are after. In general your property is not something the EF runtime can really use - I believe it will be just treated as an extension and ignored. If you want to add a property that is supposed to be used by the EF runtime the property must be declared in the CSDL (conceptual layer) and SSDL (store layer) and mapped correctly in the MSL (mapping layer) - with the latter being probably the most difficult.
Unless I am missing what you are trying to achieve you are probably using a wrong extension point. The IEntityDesignerExtendedProperty allows defining custom properties that shows in the property and the model browser windows in the designer but are ignored at runtime. For me it looks like you would like to add a property automatically to your model. For that I would try using the IModelTransformationExtension where you should be given the entire edmx which you will be able to modify at will (i.e. CSDL, SSDL, MSL and add elements (properties) in correct EF xml namespaces). I would try using OnBeforeModelSaved since I believe the model will be saved automatically before trying to generate the database.
I was able to achieve what I want using the edmx:CopyToSSDL=true attribute:
<edmx:ConceptualModels>
<Schema ...>
....
<EntityType Name="Entity1">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Type="Guid" Name="Id" Nullable="false" annotation:StoreGeneratedPattern="None" />
<Property Type="String" Name="Name" Nullable="false" />
<a:MyNewProperty edmx:CopyToSSDL="true"
xmlns:a="http://schemas.tempuri.com/MyNewProperty">
True
</a:MyNewProperty>
</EntityType>
This way, the transformator that generates the SSDL from the CSDL copies the annotation over to the SSDL, so I'm able to access it in the T4 template that generates the DDL SQL file.
If someone is going to use this in Entity Framework 5, please not that there is a bug (http://entityframework.codeplex.com/workitem/702), and you can workaround by using the old EDMX XML namespace:
<a:MyNewProperty edmxv2:CopyToSSDL="true"
xmlns:a="http://schemas.tempuri.com/MyNewProperty"
xmlns:edmxv2="http://schemas.microsoft.com/ado/2008/10/edmx">
True
</a:MyNewProperty>

Reset ADO.NET schema

I modified a schema (set a field to be non-nullable), but when I try to recreate the mapping with ADO.NET I only see the old schema.
The .edmx file looks like this:
<EntityType Name="STG_DW_BUF_CODE_D">
<Key>
<PropertyRef Name="BUF_CODE_KEY" />
</Key>
<Property Name="BUF_CODE_KEY" Type="number" Nullable="false" />
…
<EntityType Name="STG_DW_REGION_D">
<Property Name="REGION_KEY" Type="number" />
The STG_DW_REGION_D view should have Nullable="false" like the view above it.
I can confirm the new schema has this field non-nullable through another SQL application, but I can't get ADO.NET to notice.
I tried erasing the model and recreating it. I tried closing visual studio and starting it up again. It still sees the old schema.
Does anyone know how to reset it? Any suggestions?
This is either a bug in ADO.NET or ODP (Oracle's connection to Linq). If you add a field it will remove the cached schema and pull in a new schema with updated field attributes.