Cannot update Entity Framework virtual list objects - c#

I'll try explain simply my Entity Framework model. I have a User object which has a collection of zero or more UserInterest objects. Each user interest object has only three properties, unique ID, User Id and Description.
Whenever the user updates the User object, it should also update the related UserInterest objects but because these are free form (ie not part of a list of allowed interests), I want the user to pass in a list of type "string" to the webmethod of the names of all their interests. The code would ideally then look at the users existing list of interests, remove any that were no longer relevant and add in new ones and leave the ones which already exist.
My object model definitions
[Table("User")]
public class DbUser {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public virtual IList<DbUserInterest> Interests { get; set; }
}
[Table("UserInterest")]
public class DbUserInterest : IEntityComparable<DbUserInterest>
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public virtual DbUser User { get; set; }
public int? UserId { get; set; }
}
The context Fluent mappings
modelBuilder.Entity<DbUser>()
.HasKey(u => u.UserId);
modelBuilder.Entity<DbUser>()
.Property(u => u.UserId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<DbUser>()
.HasMany(u => u.Interests)
.WithRequired(p => p.User)
.WillCascadeOnDelete(true);
modelBuilder.Entity<DbUserInterest>()
.HasKey(p => p.Id);
modelBuilder.Entity<DbUserInterest>()
.Property(p => p.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<DbUserInterest>()
.HasRequired(p => p.User)
.WithMany(u => u.Interests)
.WillCascadeOnDelete(true);
And lastly my webmethod code and repository method to do the update
public UpdateUserProfileDetailsResponse UpdateUserProfileDetails(UpdateUserProfileDetailsRequest request)
{
try
{
var dbItem = _userDataRepository.GetItem(request.Header.UserId);
dbItem.Interests.Clear();
foreach (var dbInterest in request.UserInterests)
dbItem.Interests.Add(new DbUserInterest { Name = dbInterest, UserId = dbItem.UserId});
_userDataRepository.UpdateItem(dbItem);
_userDataRepository.Save();
}
catch (Exception ex)
{
}
}
public override bool UpdateItem(DbUser item)
{
var dbItem = GetItem(item.UserId);
if (dbItem == null)
throw new DataRepositoryException("User not found to update", "UserDataRepository.UpdateItem");
var dbInterests = Work.Context.UserInterests.Where(b => b.UserId == item.UserId).ToList();
var interestsToRemove = (from interest in dbInterests let found = item.Interests.Any(p => p.IsSame(interest)) where !found select interest).ToList();
var interestsToAdd = (from interest in item.Interests let found = dbInterests.Any(p => p.IsSame(interest)) where !found select interest).ToList();
foreach (var interest in interestsToRemove)
Work.Context.UserInterests.Remove(interest);
foreach (var interest in interestsToAdd)
{
interest.UserId = item.UserId;
Work.Context.UserInterests.Add(interest);
}
Work.Context.Entry(dbItem).State = EntityState.Modified;
return Work.Context.Entry(dbItem).GetValidationResult().IsValid;
}
When I run this, at the Repository.Save() line I get the exception
Assert.IsTrue failed. An unexpected error occurred: An error occurred while updating the entries. See the inner exception for details.
But interestingly in the webmethod if I comment out the line dbItem.Interests.Clear(); it doesn't throw an error, although then of course you get duplicates or extra items as it thinks everything is a new interest to add. However removing this line is the only way I can get the code to not error
Before, I had the UserId property of the Interest object set to non nullable and then the error was slightly different, something about you cannot change the relationship of a foreign key entity that is non nullable, which is why I changed the property to nullable but still no go.
Any thoughts?

You can't just clear the collection and then try to rebuild it. EF doesn't work that way. The DbContext keeps track of all of the objects that were brought back from the database in its Change Tracker. Doing it your way will of course cause duplicates because EF sees that they're not in the Change Tracker at all so they must be brand new objects necessitating being added to the database.
You'll need to either do the add/remove logic in your UpdateUserProfileDetails method, or else you have to find a way to pass request.UserInterests into your UpdateItem method. Because you need to adjust the existing entities, not the ones found on the request (which EF thinks are new).

you could try in this way
remove
dbItem.Interests.Clear();
then
foreach (var dbInterest in request.UserInterests){
if(dbItem.Interests.Any()){
if (dbItem.Interests.Count(i=> i.Name==dbInterest && i.UserId==dbItem.UserId) == 0){
dbItem.Interests.Add(new DbUserInterest { Name = dbInterest, UserId = dbItem.UserId});
}
}
}

Related

Updating many-to-many links with direct relationships

I am using Entity Framework Core 5 in a .NET 5.0 web app. I have two classes that are joined in a many-to-many relationship. I am using 'direct' approach, as described here. This mean that I do not have joining tables explicitly defined in code; instead, EF has inferred the relationship from the following schema:
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public ICollection<Group> Groups { get; set; }
}
public class Group
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public ICollection<User> Users { get; set; }
}
I wish to be able to update a 'Group' or 'User' by simply providing EF with a new object. For example, if I provide EF with a 'Group' object that has the same ID as a Group already in the database, but it now has an extra 'User' in the Users collection, then I would expect EF to update the 'UserGroup' table that it has made behind the scenes with a new record to represent this relationship.
Here is what I have tried:
1.
public void Update(Group group)
{
Group oldGroup = _context.Groups
.Include(g => g.Users)
.First(g => g.ID == group.ID);
_context.Entry(oldGroup).CurrentValues.SetValues(group);
_context.SaveChanges();
}
Result: Saves changes to any props belonging to the Group object, but does not affect any relationships.
2.
public void Update(Group group)
{
Group oldGroup = _context.Groups
.Include(g => g.Users)
.First(g => g.ID == group.ID);
oldGroup.Users = group.Users;
_context.Entry(oldGroup).CurrentValues.SetValues(group);
_context.SaveChanges();
}
Result: Exception.
System.InvalidOperationException: 'The instance of entity type 'User' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.'
I added some extension methods for Intersect and LeftComplementRight from here and tried calculating the diffs myself.
public void Update(Group group)
{
Group oldGroup = _context.Groups
.Include(g => g.Users)
.First(g => g.ID == group.ID);
var oldUsers = oldGroup.Users;
var newUsers = group.Users;
var toBeRemoved = oldUsers.LeftComplementRight(newUsers, x => x.ID);
var toBeAdded = newUsers.LeftComplementRight(oldUsers, x => x.ID);
var toBeUpdated = oldUsers.Intersect(newUsers, x => x.ID);
foreach (var u in toBeAdded)
{
oldGroup.Users.Add(u);
}
foreach (var u in toBeRemoved)
{
oldGroup.Users.Remove(u);
}
_context.Entry(oldGroup).CurrentValues.SetValues(group);
_context.SaveChanges();
}
Result: Same exception as above.
It was at this point I realised that the 'User' objects that compose the 'Users' collection had the 'Groups' object instantiated and populated with self-references back to the Group object. I realised that this was probably confusing EF, so I tried this:
4.
public void Update(Group group)
{
Group oldGroup = _context.Groups
.Include(g => g.Users)
.First(g => g.ID == group.ID);
var oldUsers = oldGroup.Users;
var newUsers = group.Users;
var toBeRemoved = oldUsers.LeftComplementRight(newUsers, x => x.ID);
var toBeAdded = newUsers.LeftComplementRight(oldUsers, x => x.ID);
var toBeUpdated = oldUsers.Intersect(newUsers, x => x.ID);
foreach (var u in toBeAdded)
{
u.Groups = null;
oldGroup.Users.Add(u);
}
foreach (var u in toBeRemoved)
{
u.Groups = null;
oldGroup.Users.Remove(u);
}
_context.Entry(oldGroup).CurrentValues.SetValues(group);
_context.SaveChanges();
}
This works, however it seems like I'm doing far too much work. I anticipate having multiple many-to-many relationships throughout this project, and I don't want to have to duplicate this code on every update method. I suppose I could create an Extension method, but I feel like EF should be able to handle this common usecase.
Am I missing something?
You can try this. Basically just replaces the entities (the ones with matching IDs) with the currently tracked instances. The new entities in the list are automatically persisted when you save. And the entities which were present in the original list but aren't present in the new list, will be automatically removed.
public void Update(Group updatedGroup)
{
Group group = _context.Groups
.Include(g => g.Users)
.First(g => g.ID == updatedGroup.ID);
group.Users = updatedGroup.Users
.Select(u => group.Users.FirstOrDefault(ou => ou.ID == u.ID) ?? u)
.ToList();
// Do other changes on group as needed.
_context.SaveChanges();
}
Yeah, it's kind of ridiculous that there is no simple method to replace a list of entities in this way. Although it gets less ridiculous if you consider that normally we don't even get entity instances in our update-like methods, because usually we get the changes through DTOs, so we couldn't just replace a list to begin with.
Let me know if it doesn't work (it's late here). :)

Violation of PRIMARY KEY constraint 'PK_XY'. Cannot insert duplicate key in object 'dbo.XY'

Using EF Core, I have a Zone that can have multiple Sol (soils), same Sol can be attached to multiple Zone:
public class Sol
{
// ...
public ICollection<Zone> Zones { get; set; } = new List<Zone>();
}
public class Zone
{
// ...
public ICollection<Sol> Sols { get; set; } = new List<Sol>();
}
public override void Configure(EntityTypeBuilder<Zone> builder)
{
// ...
builder
.HasMany(p => p.Sols)
.WithMany(p => p.Zones);
}
When adding my Sols to a Zone however I get the following exception:
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
Microsoft.Data.SqlClient.SqlException (0x80131904): Violation of PRIMARY KEY constraint 'PK_SolZone'. Cannot insert duplicate key in object 'dbo.SolZone'. The duplicate key value is (1, 2).
Some details of implementation:
In my controller, when retrieving the object I do
public override async Task<IActionResult> Edit(int id, [FromQuery] bool fullView)
{
var currentZone = _repository.SingleOrDefault(new GetZoneWithPaysAndSolsSpecification(id));
where
public class GetZoneWithPaysAndSolsSpecification : Specification<Zone>
{
public GetZoneWithPaysAndSolsSpecification(int zoneId)
{
Query.Where(p => p.Id == zoneId);
Query.Include(p => p.Pays);
Query.Include(p => p.Sols);
}
}
before updating the object, I convert my ZoneDTO to Zone and then add it to the database:
protected override void BeforeUpdate(Zone zone, ZoneDTO sourceDto)
{
zone.Sols.Clear();
foreach (var solId in sourceDto.SolIds)
{
var sol = _repository.GetById<Sol>(solId);
zone.Sols.Add(sol);
}
base.BeforeUpdate(zone, sourceDto);
}
I use the base controller, that uses the BeforeUpdate, like this
[HttpPost]
[ValidateAntiForgeryToken]
public virtual async Task<IActionResult> Edit(TDto dto)
{
try
{
var entity = FromDto(dto);
BeforeUpdate(entity, dto);
await _repository.UpdateAsync(entity);
return RedirectToAction(nameof(Index));
}
catch (Exception ex)
{
_logger.LogError(ex, "when editing an object after submit");
return PartialView();
}
}
The repository code
public Task UpdateAsync<T>(T entity) where T : BaseEntity
{
_dbContext.Entry(entity).State = EntityState.Modified;
return _dbContext.SaveChangesAsync();
}
I use AutoMapper
protected TBussinesModel FromDto(TDto dto)
{
return _mapper.Map<TBussinesModel>(dto);
}
And the mapping is like this
CreateMap<Zone, ZoneDTO>()
.ForMember(p => p.SolIds, o => o.MapFrom(p => p.Sols.Select(s => s.Id).ToArray()))
.ForMember(p => p.SolNoms, o => o.MapFrom(p => p.Sols.Select(s => s.Nom).ToArray()))
.ReverseMap();
When you are mapping from dto to entity, your FromDto method is giving you a Zone entity whose Sols list is not populated with the zone's existing sols. Its an empty list. So, when you are calling -
zone.Sols.Clear();
its doing nothing, and at database level, the zone still holds its sols. Then when you are re-populating the Sols list, you are trying to insert some previously existing Sol to the list.
You have to fetch the existing Zone along with its Sols list from the database, before clearing and repopulating it. How you can do that depends on how your repository is implemented.
On how to update many-to-many entity in EF 5.0, you can check this answer
EDIT :
Try modifying your base controller's Edit method as -
[HttpPost]
[ValidateAntiForgeryToken]
public virtual async Task<IActionResult> Edit(TDto dto)
{
try
{
var zone = _repository.SingleOrDefault(new GetZoneWithPaysAndSolsSpecification(dto.Id));
zone.Sols.Clear();
foreach (var id in dto.SolIds)
{
var sol = _repository.GetById<Sol>(solId);
zone.Sols.Add(sol);
}
await _repository.UpdateAsync(zone);
return RedirectToAction(nameof(Index));
}
catch (Exception ex)
{
_logger.LogError(ex, "when editing an object after submit");
return PartialView();
}
}
Not related to your issue :
You are fetching one Sol a time inside a loop -
foreach (var id in dto.SolIds)
{
var sol = _repository.GetById<Sol>(solId);
zone.Sols.Add(sol);
}
which is not efficient at all. Try something like -
var sols = // fetch all the sols from the database
foreach (var id in dto.SolIds)
{
zone.Sols.Add(sols.FirstOrDefault(p => p.Id == id));
}
Since the relationship between SOL and Zone is a many to many relationship a Separate table containing the primary keys of both the tables will be created.
A table definition is needed for this many to many relationship. Also Try including the UsingEnity to specify the entity used for defining the relationship
modelBuilder
.Entity<Post>()
.HasMany(p => p.SOL)
.WithMany(p => p.ZONE)
.UsingEntity(j => j.ToTable("SOL_ZONE"));
It seems to me that you have many to many relationship. So you need to set up a new entity with primary key to be composite for mapping and to configure the entities the right way:
(In EF Core up to and including 3.x, it is necessary to include an entity in the model to represent the join table, and then add navigation properties to either side of the many-to-many relations that point to the join entity instead:)
public class Sol
{
// ...
public ICollection<SolZone> SolZones { get; set; } = new List<Zone>();
}
public class Zone
{
// ...
public ICollection<SolZone> SolZones { get; set; } = new List<Sol>();
}
public class SolZone
{
public int SolId { get; set; }
public Sol Sol { get; set; }
public int ZoneId { get; set; }
public Zone Zone { get; set; }
}
// And in the OnModelCreating
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<SolZone>().HasKey(sc => new {sc.SolId , sc.ZoneId});
modelBuilder.Entity<SolZone>()
.HasOne<Sol>(s => s.Sol)
.WithMany(sz => sz.SolZones)
.HasForeignKey(s => s.SolId)`
modelBuilder.Entity<SolZone>()
.HasOne<Zone>(z => z.Zone)
.WithMany(sz => sz.SolZones)
.HasForeignKey(z => z.ZoneId);
}
The primary key for the join table is a composite key comprising both of the foreign key values. In addition, both sides of the many-to-many relationship are configured using the HasOne, WithMany and HasForeignKey Fluent API methods.
This is sufficient if you want to access Sol Zone data via the Sol or Zone entities. If you want to query SolZone data directly, you should also add a DbSet for it:
public DbSet<SolZone> SolZones { get; set; }
You can look up different relationships in EF Core here: https://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key

Delete entity with all childs connected

I have been trying to remove single Item from database properly, to satisfy FK restrictions.
I got Item entity (for using in EF) which get referred by multiple others
public class Item
{
public int Id {get; set;}
...
public FoodItem FoodItem { get; set; }
public LocalItem LocalItem { get; set; }
public ItemToCategory ItemToCategory { get; set; }
}
Where Id is PK for Item and FK for other entities.
I see two possible approaches to delete entity and childs:
Get connected entity through LINQ query;
Give CascadeDelete constraint to my Item.
First:
As I found this far, my query must be like
Item basicItem = await db.Items.Include(b => b.LocalItem)
.Include(b => b.FoodItem)
.Include(b => b.ItemToCategory)
.SingleOrDefaultAsync(p => p.Id == productId);
db.Items.Remove(basicItem);
await db.SaveChangesAsync();
But I get Source sequence contains more than one element error and have no idea why.
Second:
It's still a way, but I want to suspend it for a while. Cause I see it, like less safe approach.
So, returning back to topic: How can I get my entity deleted with all childs connected?
At the end, I choose to set cascade delete constraint on connected entites

Null returned when trying to access child object

I am in the process of migrating to EF6 from Linq To Sql, and I have the autogenerated object
public partial class PCU
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public PCU()
{
this.PUs = new HashSet<PU>();
}
public int ID { get; set; }
public int FileNumberID { get; set; }
public Nullable<int> PartnerID { get; set; }
public virtual Company Company { get; set; }
public virtual File File { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<PU> PUs { get; set; }
}
where PartnerID is the Foreign key for company
when I call:
var company = dc.Set<PCU>().FirstOrDefault(c => c.FileNumber == fileNumber).Company;
I get a Null object, however if I call:
var company = dc.Set<PCU>().Where(c => c.FileNumber == fileNumber).Select(x => x.Company).First();
It returns the company object as expected. I have both LazyLoading and ProxyCreation enabled.
I understand I could use:
var company = dc.Set<PCU>().Include(x => x.Company).FirstOrDefault(c => c.FileNumber == fileNumber).Company;
however, as this is existing code, and I have the same problem for hundreds of different objects, this will mean massive amounts of changes. Is there an easier way to achieve this?
At first it indeed looks like:
dc.Set<PCU>().FirstOrDefault(c => c.FileNumber == fileNumber).Company
is similar to:
dc.Set<PCU>().Where(c => c.FileNumber == fileNumber).Select(x => x.Company).First()
but in case the foreign key 'Company' is null while using 'FirstOrDefault', returning 'Company' will obviously return null.
The second case, selects a valid 'Company' FK from the result set which was created by the 'Where' condition, and returns the first one from that set, this is why the 'Where' query returns a 'Company'.
If you don't wish to alter existing code, it seems to me that the best solution for you will be to actually see why you have null foreign keys in your database in the first place.
If it's the way its supposed to be (e.g., a null 'Company' entry could exist) then you'll have to take it into account in your queries, hence changing them to return only existing 'Company' entries.
Edit: I take it back, I missed the 'LazyLoading enabled' part šŸ¤”
As a follow up, I believe the cause of the error is the name of the ForeignKey (PartnerID), and if it were named "CompanyID" it would work fine.
I have had to bite the bullet, and had to implement
var company = dc.Set<PCU>().Include(x => x.Company).FirstOrDefault(c => c.FileNumber == fileNumber).Company;
where neccesary. There does not seem to be another workaround, except for renaming the columns in my DB (which I can't do).

Generating Wrong Columns on Queries

We are having an intermittent problem with NHibernate where it will occasionally generate a query with a wrong column on the SQL. If we restart the application the problem ceases to happen (sometimes it requires more than one restart). When the problem occurs, during the lifetime of that process, it always produces the wrong SQL for the affected entity. ItĀ“s not always the same affected entity.
ItĀ“s an ASP.NET application where the SessionFactory is created during the Application_Start event. All the configuration and mapping are done by code.
We donĀ“t have any more ideas how to test or debug the application, and IĀ“m starting to assume thereĀ“s some bug in NHibernate, since the application fixes itself upon restart. Any ideas/tips will be much appreciated!
HereĀ“s an example:
Entity
namespace Example.Clinicas
{
public partial class Clinica : Entidade // Abstract base class that has a property Handle
{
public virtual string Ddd { get; set; }
public virtual string Ddd2 { get; set; }
public virtual long? Duracao { get; set; }
public virtual string Numero { get; set; }
public virtual string Numero2 { get; set; }
public virtual string Prefixo { get; set; }
public virtual string Prefixo2 { get; set; }
public virtual long? HandlePrestador { get; set; }
public virtual Example.Prestadores.Prestador Prestador { get; set; }
}
}
Mapping
namespace Example.Clinicas.Mappings
{
public class ClinicaMapping : ClassMapping<Clinica>
{
public ClinicaMapping()
{
Table("CLI_CLINICA");
Id(x => x.Handle, map =>
{
map.Column("HANDLE");
map.Generator(Generators.Sequence, g => g.Params(new { sequence = "SEQ_AUTO1816" }));
});
Property(x => x.Ddd, map => map.Column( c=>
{
c.Name("DDD1");
c.Length(4);
}));
Property(x => x.Ddd2, map => map.Column( c=>
{
c.Name("DDD2");
c.Length(4);
}));
Property(x => x.Duracao, map => map.Column("INTERVALOAGENDA"));
Property(x => x.Numero, map => map.Column( c=>
{
c.Name("NUMERO1");
c.Length(5);
}));
Property(x => x.Numero2, map => map.Column( c=>
{
c.Name("NUMERO2");
c.Length(5);
}));
Property(x => x.Prefixo, map => map.Column( c=>
{
c.Name("PREFIXO1");
c.Length(5);
}));
Property(x => x.Prefixo2, map => map.Column( c=>
{
c.Name("PREFIXO2");
c.Length(5);
}));
Property(x => x.HandlePrestador, map => map.Column("PRESTADOR"));
ManyToOne(x => x.Prestador, map =>
{
map.Column("PRESTADOR");
map.Insert(false);
map.Update(false);
});
}
}
}
Command
Session.Query<Clinica>().FirstOrDefault();
Generated SQL
select HANDLE489_,
DDD2_489_,
DDD3_489_,
INTERVAL4_489_,
NUMERO5_489_,
NUMERO6_489_,
PREFIXO7_489_,
FATURADE8_489_,
PRESTADOR489_
from (select clinica0_.HANDLE as HANDLE489_,
clinica0_.DDD1 as DDD2_489_,
clinica0_.DDD2 as DDD3_489_,
clinica0_.INTERVALOAGENDA as INTERVAL4_489_,
clinica0_.NUMERO1 as NUMERO5_489_,
clinica0_.NUMERO2 as NUMERO6_489_,
clinica0_.PREFIXO1 as PREFIXO7_489_,
clinica0_.FATURADEPARCELAMENTO as FATURADE8_489_,
clinica0_.PRESTADOR as PRESTADOR489_
from CLI_CLINICA clinica0_)
where rownum <= 1
Exception
ORA-00904: "CLINICA0_"."FATURADEPARCELAMENTO": invalid identifier
Interesting Observations:
It is more likely to affect bigger entities (that has a higher number of properties), but also affects smaller entities occasionally;
The generated SQL always have the same number of columns as mapped properties;
The columns on the SQL are in the same order as the mapped properties on the mapping class;
The wrong column will replace an existing one;
The wrong column is a valid column in a different mapped entity;
There is no relationship between the affected entity and the one that has the wrong column;
Other Details:
.NET Version: 4.0
NHibernate Version: 3.3.3.400
Mapping by Code: NHibernate.Mapping.ByCode
Configuration by Code: NHibernate.Cfg
Load Mappings
var mapper = new ModelMapper();
foreach (var assembly in resolver.GetAssemblies()) // resolver is a class that gets all the assemblies for the current application
mapper.AddMappings(assembly.GetExportedTypes());
var mapping = mapper.CompileMappingForAllExplicitlyAddedEntities();
return mapping;
SessionFactory Configuration
var configure = new Configuration();
configure.DataBaseIntegration(x =>
{
x.Dialect<Oracle10gDialect>(); // Custom class
x.ConnectionString = ConnectionString;
x.BatchSize = 100;
x.Driver<OracleMultiQueryDataClientDriver>(); // Custom class
x.MaximumDepthOfOuterJoinFetching = 10;
x.Timeout = 250;
x.PrepareCommands = true;
x.HqlToSqlSubstitutions = "true 'S', false 'N', yes 'S', no 'N'";
x.LogFormattedSql = true;
x.LogSqlInConsole = true;
x.AutoCommentSql = true;
x.IsolationLevel = IsolationLevel.ReadCommitted;
x.ConnectionProvider<ConnectionProvider>(); // Custom class
});
configure.Properties.Add(new KeyValuePair<string, string>("hibernate.command_timeout", "250"));
configure.Proxy(x => x.ProxyFactoryFactory<NHibernate.Bytecode.DefaultProxyFactoryFactory>());
configure.LinqToHqlGeneratorsRegistry<LinqToHqlGeneratorsRegistry>();
configure.CurrentSessionContext<NHibernate.Context.WebSessionContext>();
var mapping = GetMappings(); // Method showed above
mapping.autoimport = false;
configure.AddMapping(mapping);
var listener = new AuditEventListener();
configure.EventListeners.PostInsertEventListeners = new IPostInsertEventListener[] { listener };
configure.EventListeners.PostUpdateEventListeners = new IPostUpdateEventListener[] { listener };
configure.SessionFactory().GenerateStatistics();
return configure;
I asked the same question on the NHibernate Users Google Groups forum, and someone thinks they have worked out the root cause (and have also proposed a solution):
https://groups.google.com/forum/#!topic/nhusers/BZoBoyWQEvs
The problem code is in PropertyPath.Equals(PropertyPath) which attempts to determine equality by only using the hash code. This works fine for smaller code bases as the default Object.GetHashCode() returns a sequential object index. However, after garbage collection, these indices get reused as finalized objects are removed and new objects are created...which results in more than one object getting the same hashcode...Once garbage collection kicks in, property paths have a chance to share the same hashcode which means they will ultimately mix up their customizers for the colliding properties, thus the wrong column names...
If you want to fix this the bug, you can patch the NH source code:
If you have your own copy of the NH source, you can fix the bug by changing NHibernate/Mapping/ByCode/PropertyPath.cs line #66 from:
return hashCode == other.GetHashCode();
To:
return hashCode == other.GetHashCode() && ToString() == other.ToString();
Please check out the Google Group for full details of the issue.
it looks like the "creditcard payments" FATURADEPARCELAMENTO is a property on your "lender" object PRESTADOR, if this is the case it needs to be a reference and NOT a property in the mapping. Hope that helps or at least gets you pointed in the correct direction
the reference would take the place of your line
Property(x => x.HandlePrestador, map => map.Column("PRESTADOR"));
and would be something close to
References(x => x.HandlePrestador)
Check your querylog to see what type of query its runnig, in your sql from there you, can spot the problem.

Categories

Resources