We encountered trouble on our model while running pivot file. It appears that we suffered from the naming collision on our PK and FK's (like in this).
Our use case was such that we created new table (say tableAB), but because of the naming collision, the PK (say PK_tableA) wasn't created because another table (say tableAB) had already this PK (PK_tableA) defined. The pivot would stop processing because of a FK referencing non-existing PK on tableAB.
Is there any way to rename PK and FK constraint ? In particular we already use a custom class overriding SoftFluent.Samples.CustomNamingConvention.FormatNamingConvention, but this only seems to apply to DF_ constraints (see here).
I would like to achieve same things with PK and FK constraints, what is the best solution ?
EDIT 08/07/2016
Finally found my way with the object casting. This code below generates the constraint with the expected naming:
public override string GetShortName(IShortNamedObject obj, IDictionary context)
{
Object objCopy = obj;
if (objCopy is Constraint)
{
Constraint constraint = objCopy as Constraint;
if (constraint != null && constraint.Table != null)
{
if (constraint.ConstraintType == ConstraintType.PrimaryKey || constraint.ConstraintType == ConstraintType.ForeignKey)
{
var result = constraint.Table.Name + base.GetShortName(obj, context);
return result;
}
}
}
return base.GetShortName(obj, context);
}
Thanks for the support provided.
A naming convention can support many different types of names (full, short, display, etc.) depending on the type of abstract concept (constraint, table, procedure, etc.) processed by the inference pipeline, this is how is defined CodeFluent's INamingConvention (in the CodeFluent.Model.Common.Naming namespace):
public interface INamingConvention
{
string GetName(INamedObject obj, IDictionary context);
string GetShortName(IShortNamedObject obj, IDictionary context);
string GetFullName(IFullNamedObject obj, IDictionary context);
string GetDisplayName(IDisplayNamedObject obj, IDictionary context);
string GetClrFullTypeName(IClrFullTypeNamedObject obj, IDictionary context);
string GetPersistenceName(IPersistenceNamedObject obj, IDictionary context);
string GetPersistenceShortName(IPersistenceShortNamedObject obj, IDictionary context);
}
Constraints use short names and full names, so if you derive from FormatNamingConvention (which implements INamingConvention), you should override the GetShortName and/or GetFullName methods and cast obj as a Constraint.
Note it is unusual to have so many problems with short names, or maybe all your entities start with the same names over and over accross model versions. You can also tweak the short name length (wich is by default set to 3) with the persistenceShortNameLength attribute: CodeFluentRuntimeException (CF1024): Cannot determine short name
Related
I have my entity defined like this:
public class Entity : BaseModel // Has the already ID defined
{
private int? companyId;
public Company? Company { get; set; }
public int? CompanyId {
get => this.companyId == 0 ? null : this.companyId; // I tried this for debugging purposes to force this value to "null" -> made no difference
set => this.companyId = value;
}
}
public class Company : BaseModel // Has the already ID defined
{
public IEnumerable<Entity> Entities { get; set; } = new List<Entity>();
}
Anyway, if I set the CompanyId to null, my DB throws an exception with the message: "FOREIGN KEY constraint failed". If the CompanyId is set to, e.g. 123, the relationship is resolved accordingly.
I mean, it makes sense, that EF cannot find null in my DB, but how do I want to set an optional value otherwise? I am using code first annotations only, hence my OnModelCreating of my context is completely empty.
How are you loading the entities in the first place? Are you loading an Entity by ID and trying to dis-associate it from a company, or have you loaded a company with it's entities and trying to remove one association?
Normally when working with relations where you have navigation properties, you want to de-associate them (or delete them) via the navigation properties, not the FK properties. For instance if loading a company and wanting to de-associate one of the entities you should eager-load the entities then remove the desired one from the collection:
var company = _context.Companies.Include(c => c.Entitites).Single(c => c.Id == companyId);
var entityToRemove = company.Entities.SingleOrDefault(e => e.Id == entityId);
if(entityToRemove != null)
company.Entities.Remove(entityToRemove);
_context.SaveChanges();
Provided that the relationship between Company and Entity is set up properly as an optional HasMany then provided these proxies are loaded, EF should work out to set the entityToRemove's FK to null.
If you want to do it from the Entity side:
var entityToRemove = _context.Entities.Include(e => e.Company).Single(e => e.Id == entityId);
entityToRemove.Company = null;
_context.SaveChanges();
That too should de-associate the entities. If these don't work then it's possible that your mapping is set up for a required relationship, though I am pulling this from memory so I might need to fire up an example to verify. :) You also should be checking for any code that might set that CompanyId to 0 when attempting to remove one, whether that might be happening due to some mapping or deserialization. Weird behaviour like that can occur when entities are passed around in a detached state or deserialized into controller methods. (which should be avoided)
Update: Code like this can be very dangerous and lead to unexpected problems like what you are encountering:
public virtual async Task<bool> Update(TModel entity)
{
Context.Update(entity);
await Context.SaveChangesAsync();
return true;
}
Update() is typically used for detached entities, and it will automatically treat all values in the entity as Modified. If model was already an entity tracked by the Context (and the context is set up for change tracking) then it is pretty much unnecessary. However, something in the calling chain or wherever has constructed the model (i.e. Entity) has set the nullable FK to 0 instead of #null. This could have been deserialized from a Form etc. in a view and sent to a Controller as an integer value based on a default for a removed selection. Ideally entity classes should not be used for this form of data transfer from view to controller or the like, instead using a POCO view model or DTO. To correct the behaviour as your code currently is, you could try the following:
public async Task<bool> UpdateEntity(Entity entity)
{
var dbEntity = Context.Set<Entity>().Include(x => x.Customer).Single(x => x.Id == entityId);
if (!Object.ReferenceEquals(entity, dbEntity))
{ // entity is a detached representation so copy values across to dbEntity.
// TODO: copy values from entity to dbEntity
if(!entity.CustomerId.HasValue || entity.CustomerId.Value == 0)
dbEntity.Customer = null;
}
await Context.SaveChangesAsync();
return true;
}
In this case we load the entity from the DbContext. If this method was called with an entity tracked by the DbContext, the dbEntity would be the same reference as entity. In this case with change tracking the Customer/CustomerId reference should have been removed. We don't need to set entity state or call Update. SaveChanges should persist the change. If instead the entity was a detached copy deserialized, (likely the case based on that 0 value) the reference would be different. In this case, the allowed values in the modified entity should be copied across to dbEntity, then we can inspect the CustomerId in that detached entity for #null or 0, and if so, remove the Customer reference from dbEntity before saving.
The caveats here are:
This won't work as a pure Generic implementation. To update an "Entity" class we need knowledge of these relationships like Customer so this data service, repository, or what-have-you implementation needs to be concrete and non-generic. It can extend a Generic base class for common functionality but we cannot rely on a purely Generic solution. (Generic methods work where implementation is identical across supported classes.)
This also means removing that attempt at trying to handle Zero in the Entity class. It should just be:
public class Entity : BaseModel
{
public Company? Company { get; set; }
[ForeignKey("Company")]
public int? CompanyId { get; set; }
// ...
}
Marking Foreign Keys explicitly is a good practice to avoid surprises when you eventually find yourself needing to break conventions that EF accommodates in simple scenarios.
When there is an #EmbeddedId field, a custom Field Bridge should be implemented.
There is a Feature opened about it https://hibernate.atlassian.net/browse/HSEARCH-1879. But it isn't ready yet.
In this case, the interface correct to implement is TwoWayFieldBridge?
Below is my implementation for a composite ID with 5 fields.
public class ChavePrimariaAcompanhamentoBridge implements TwoWayFieldBridge {
#Override
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
AcompanhamentoPK chavePrimaria = (AcompanhamentoPK) value;
Integer ano = chavePrimaria.getAno();
Integer mes = chavePrimaria.getMes();
Long codigoCredenciada = chavePrimaria.getCredenciada().getCodigo();
Long codigoPosto = chavePrimaria.getPostoAtendimento().getCodigo();
Integer numeroSequencial = chavePrimaria.getNumeroSequencial();
luceneOptions.addNumericFieldToDocument("mes", mes, document);
luceneOptions.addNumericFieldToDocument("ano", ano, document);
luceneOptions.addNumericFieldToDocument("credenciada.codigo", codigoCredenciada, document);
luceneOptions.addNumericFieldToDocument("postoAtendimento.codigo", codigoPosto, document);
luceneOptions.addNumericFieldToDocument("numeroSequencial", numeroSequencial, document);
}
#Override
public Object get(String name, Document document) {
AcompanhamentoPK chavePrimaria = new AcompanhamentoPK();
chavePrimaria.setMes(Integer.valueOf(document.get("mes")));
chavePrimaria.setAno(Integer.valueOf(document.get("ano")));
chavePrimaria.setCredenciada(new Credenciada(Long.valueOf(document.get("credenciada.codigo"))));
chavePrimaria.setPostoAtendimento(new CadastroPostoAtendimento(Long.valueOf(document.get("postoAtendimento.codigo"))));
chavePrimaria.setNumeroSequencial(Integer.valueOf(document.get("numeroSequencial")));
return chavePrimaria;
}
#Override
public String objectToString(Object value) {
AcompanhamentoPK chavePrimaria = (AcompanhamentoPK) value;
return chavePrimaria.toString();
}
}
1 - Is there another best way to make it?
2 - Is there any error(about concepts) in this implementation?
3 - What is the objectToString method used for? It is important?
I am making this question because I haven't found any documentation about it, so I am not sure.
EDIT: In Hibernate Search 6+, in order to map an #EmbeddedId, you should use a custom IdentifierBridge: https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/#mapper-orm-bridge-identifierbridge
Original answer for Hibernate Search 5:
In this case, the interface correct to implement is TwoWayFieldBridge?
Yes: https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/#_two_way_bridge
1 - Is there another best way to make it?
Not that I know of.
2 - Is there any error(about concepts) in this implementation?
Yes.
You shouldn't use Integer.valueOf here. Just call Field.numericValue() on the result of document.get, and cast the result to Integer.
You should also store the unique string representation of the ID in the set() method:
luceneOptions.addFieldToDocument( name, objectToString( id ), document );
3 - What is the objectToString method used for?
Hibernate Search will use the result of this method mostly to build queries, for example when it must retrieve documents that should be deleted, or when you query the ID field explicitly.
It is important?
Nothing will work unless you implement it properly, i.e. unless you make sure that:
it returns two different values for two different composite IDs
it always returns the same value for a given composite ID
I currently have an object like this (simplified):
public class Image {
public int Id { get; set; }
public int ExternalId { get; set; }
}
Now let's say I have this method (mostly pseudo-code):
public void GetImage(int externalId) {
var existingImage = db.Images.FirstOrDefault(i => i.ExternalId == externalId);
if (existingImage != null) {
return existingImage;
}
var newImage = new Image() { ExternalId = externalId };
db.Images.Attach(newImage);
db.SaveChanges();
return newImage;
}
Because ExternalId isn't a key, the change tracker won't care if I have "duplicate" images in the tracker.
So now, let's say this method gets called twice, at the same time via AJAX and Web API (my current scenario). It's async, so there are two threads calling this method now.
If the time between calls is short enough (in my case it is), two rows will be added to the database with the same external ID because neither existing check will return a row. I've greatly simplified this example, since in my real one, there's a timing issue as I fetch the "image" from a service.
How can I prevent this? I need the image to be returned regardless if it's new or updated. I've added a Unique Constraint in the database, so I get an exception, but then on the client, the call fails whereas it should use the existing image instead of throwing an exception.
If I understand EF correctly, I could handle this by making ExternalId a primary key and then use concurrency to handle this, right? Is there any way to avoid changing my current model or is this the only option?
If you already have property defining uniqueness of your entity (ExternalId) you should use it as a key instead of creating another dummy key which does not specify a real uniqueness of your entity. If you don't use ExternalId as a key you must put unique constraint on that column in the database and handle exception in your code to load existing Image from the database.
Okay, I'm really struggling with how to update a list of foreign keys in MVC2/EF4.
I have a one to many relationship between a Template object which can have many or no TemplateScenario objects.
Essentially, I have an edit method in a controller that is trying to do this:
// POST: /Modes/Edit/1
[HttpPost]
public ActionResult Edit(int id, FormCollection formValues)
{
Template template = _templateRepository.GetTemplate(id);
TemplateCreateViewModel viewModel = new TemplateCreateViewModel();
viewModel.Template = template;
viewModel.TemplateScenarioList = template.TemplateScenarios.ToList();
//Update the model
UpdateModel(viewModel);
UpdateModel(viewModel.Template.TemplateScenarios, "TemplateScenarioList", new[] { "ScenarioID", "TemplateID" });
_templateRepository.Save();
return RedirectToAction("Edit", new { id = template.TemplateID });
}
This code successfully updates the 'template' object. It also adds the 'templatescenario' child objects BUT only if it is the first time I have added 'templatescenarios' to this particular template. If any templatescenario objects already exist for a given template, and I try to update them based on the new list, I get this 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."
The _templateRepository.Save(); is just calling the entities.SaveChanges() EF4 method.
I can solve this in a dirty way by passing down a list of templatescenario ids to my repository class in a custom 'update' method that looks like this:
public void Update(Template template, IList<int> templateScenarios)
{
//Delete Old Entries
foreach (TemplateScenario ts in entities.TemplateScenarios)
{
if (ts.TemplateID == template.TemplateID)
{
if (templateScenarios == null)
entities.TemplateScenarios.DeleteObject(ts);
else if (!templateScenarios.Where(tsl => tsl == ts.ScenarioID).Any())
entities.TemplateScenarios.DeleteObject(ts);
}
}
//Don't need to add anything if they are null.
if (templateScenarios == null)
return;
//Add New Entries
foreach (int ts in templateScenarios)
{
if (!entities.TemplateScenarios.Where(tsc => tsc.ScenarioID == ts && tsc.TemplateID == template.TemplateID).Any())
{
TemplateScenario tempScenToAdd = new TemplateScenario();
tempScenToAdd.ScenarioID = ts;
tempScenToAdd.TemplateID = template.TemplateID;
entities.TemplateScenarios.AddObject(tempScenToAdd);
}
}
}
But that just feels dirty and I think I'm so close with the first, more automatic method. I've scoured the internet and found some similar posts on stackoverflow but am finding it difficult to reach that 'aha' moment.
Thanks,
Tom.
Incidently, I sorted out my problem.
The problem was my joining table was incorrectly using it's own primary key instead of using a composite key based on two foreign keys. This is obviously wrong /bad practice and EF4 and UpdateModel() don't play nice.
I had inherited the DB design from an ex-collegue and thus had taken the db design as correct without thinking too much about it. Very stupid of me, I know.
Basically I want to use a dynamic data website to maintain data in an EF4 model where the entities are in their own assembly. Model and context are in another assembly.
I tried this Entity Framework 4 + Self-Tracking Entities + ASP.NET Dynamic Data = Error
but get an "ambiguous match" error from reflection:
System.Reflection.AmbiguousMatchException was unhandled by user code
Message=Ambiguous match found.
Source=mscorlib
StackTrace:
at System.RuntimeType.GetPropertyImpl(String name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
at System.Type.GetProperty(String name)
at System.Web.DynamicData.ModelProviders.EFTableProvider..ctor(EFDataModelProvider dataModel, EntitySet entitySet, EntityType entityType, Type entityClrType, Type parentEntityClrType, Type rootEntityClrType, String name)
at System.Web.DynamicData.ModelProviders.EFDataModelProvider.CreateTableProvider(EntitySet entitySet, EntityType entityType)
at System.Web.DynamicData.ModelProviders.EFDataModelProvider..ctor(Object contextInstance, Func1 contextFactory)
at System.Web.DynamicData.ModelProviders.SchemaCreator.CreateDataModel(Object contextInstance, Func1 contextFactory)
at System.Web.DynamicData.MetaModel.RegisterContext(Func`1 contextFactory, ContextConfiguration configuration)
at WebApplication1.Global.RegisterRoutes(RouteCollection routes) in C:\dev\Puffin\Puffin.Prototype.Web\Global.asax.cs:line 42
at WebApplication1.Global.Application_Start(Object sender, EventArgs e) in C:\dev\Puffin\Puffin.Prototype.Web\Global.asax.cs:line 78
InnerException:
I came across a similar problem to this recently. It had to do with inheritance in my model. I had a Resource entity that had derived types of Person, Equipment, etc. and in those I had overridden a couple properties, but by mistake gave them different signatures. I'll describe my scenario and hopefully it will help.
To be able to debug deep enough into the framework, and see all the variable values, you will have to disable optimizations:
Link
I was seeing the Ambiguous Match error when registering the Context in Global.asax as you were:
public static void RegisterRoutes(RouteCollection routes)
{
// IMPORTANT: DATA MODEL REGISTRATION
// Uncomment this line to register an ADO.NET Entity Framework model for ASP.NET Dynamic Data.
// Set ScaffoldAllTables = true only if you are sure that you want all tables in the
// data model to support a scaffold (i.e. templates) view. To control scaffolding for
// individual tables, create a partial class for the table and apply the
// [ScaffoldTable(true)] attribute to the partial class.
// Note: Make sure that you change "YourDataContextType" to the name of the data context
// class in your application.
DefaultModel.RegisterContext(typeof(EntityModelContainer), new ContextConfiguration() { ScaffoldAllTables = true });
Stepping into the RegisterContext method, I got to System.Web.DynamicData.ModelProviders.EFDataModelProvider there is section of code that loads all the Entities in the model by traversing the inheritance hierarchy in the constuctor for EFDataModelProvider.
while (objectStack.Any()) {
EntityType entityType = objectStack.Pop();
if (entityType != null) {
// Update the entity set when we are at another root type (a type without a base type).
if (entityType.BaseType == null) {
currentEntitySet = entitySetLookup[entityType];
}
var table = CreateTableProvider(currentEntitySet, entityType);
tables.Add(table);
}
foreach (EntityType derivedEntityType in derivedTypesLookup[entityType]) {
// Push the derived entity types on the stack
objectStack.Push(derivedEntityType);
}
}
I put a breakpoint in here and was able to see that Ambiguous Match was occurring for me when calling CreateTableProvider on my Equipment entity (which was derived from Resource).
Looking back at the Stack Trace from the original exception (which I should have done in the first place!) I put a breakpoint in the constructor for System.Web.DynamicData.ModelProviders.EFTableProvider.IsPublicProperty and watched to see which property/method/whatever was causing the ambiguous match -- for me this ended up being a navigation property called Resources (Resources are themselves a hierarchy) that I had overridden in Equipment.
private static bool IsPublicProperty(Type entityClrType, string propertyName) {
var property = entityClrType.GetProperty(propertyName);
return property != null && property.GetGetMethod() != null;
}
In the partial class for Equipment, I had:
public partial class Equipment
{
public new IEnumerable<Resource> Resources
{
but in the parent class, Resource, Resources was defined as:
public virtual ICollection<Resource> Resources
{
When these Properties are being loaded by the .GetProperty(propertyName) in IsPublicProperty, they have the same name but different signatures (because I had given them different return type by mistake) so it isn't clear which shoudl be loaded based on name alone. I corrected my mistake and made Resources in my Equipment class return an ICollection, and boom -- no more ambiguous match.
Not sure if this will help or not, but if you step through in a similar way you should be able to find exactly what is causing the ambiguous match.