NHibernate one-to-one in a legacy database with a composite-key - c#

We have a legacy Database that we want to read data from using NHibernate.
The tables that we want to map are the following:
Users
PK - UserId
PK - GroupId
LocationSource
etc...
Locations
PK - UserId
PK - GroupId
PK - Source
X
Y
Every user has one or more locations. A location can be entered from different sources, which are identified by the Source column.
the Users table's LocationSource column holds the most relevant location source for that user.
In the current application we're writing, we need only the last location.
this is done mainly for performance reasons, we don't want to load all the locations (using outer join)
whenever we load a User (Lazy Loading is out of the question either).
The classes will look something like that:
public class UserKey
{
public int UserId {get;set;}
public int GroupId {get;set;
}
public class Location
{
public double X {get;set;}
public double Y {get;set;}
public LocationSource Source {get;set;}
}
public class User
{
public UserKey Id {get; set;}
public Location Location {get;set;}
}
I cannot figure out how to map the database scheme to those class.
Everything I tried failed so far.
I will appreciate your help.

Here's how I would do it.
User would contain both a List (Locations) and a reference to the current Location (Source) therefore you get the current Location per User and the historic list of a User's Location's.
User.Locations and User.Source will both lazy load by default but you can use any of the query options to eager load User.Source to get the current location for your benefit.
When you add a Location to a User via the Locations property you will obviously need to manage the Source reference as well.
If you would like the XML mapping files I can provide as well as I have used Fluent NHibernate 1.1.
public class User
{
public virtual int UserId { get; set; }
public virtual int GroupId { get; set; }
public virtual IList<Location> Locations { get; set; }
public virtual Location Source { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
return false;
var t = obj as User;
if (t == null)
return false;
if (UserId == t.UserId && GroupId == t.GroupId)
return true;
return false;
}
public override int GetHashCode()
{
return (UserId + "|" + GroupId).GetHashCode();
}
}
public class Source
{
public virtual int Id { get; set; }
}
public class Location
{
public virtual User User { get; set; }
public virtual int Id { get; set; }
public virtual Source Source { get; set; }
public virtual string X { get; set; }
public virtual string Y { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
return false;
var t = obj as Location;
if (t == null)
return false;
if (User == t.User && Source == t.Source)
return true;
return false;
}
public override int GetHashCode()
{
return (User.GetHashCode() + "|" + Id).GetHashCode();
}
}
public class UserMap : ClassMap<User>
{
public UserMap()
{
CompositeId()
.KeyProperty(x => x.UserId, "UserId")
.KeyProperty(x => x.GroupId, "GroupId");
HasMany(x => x.Locations);
References(x => x.Source).Columns("UserId", "GroupId", "LocationSource");
}
}
public class LocationMap : ClassMap<Location>
{
public LocationMap()
{
CompositeId()
.KeyReference(x => x.Source, "Source")
.KeyReference(x => x.User,"groupId","userid");
References(x => x.User).Columns("userid","groupid");
}
}
public class SourceMap : ClassMap<Source>
{
public SourceMap()
{
Id(x => x.Id).GeneratedBy.Native();
}
}

Related

Entity Framework Core (Postgres) Multiple Includes creates ghost property

I'm having an issue with a series of Include/ThenInclude in a query.
Here is my EntityFrameworkCore Query :
var fund = await funds.Where(x => x.Id == fundId)
.Include(f => f.Compositions.Where(compo => compo.Date == compositionDate))
.ThenInclude(c => c.CompositionItems)
.ThenInclude(item => item.Asset)
.FirstOrDefaultAsync(token)
?? throw new NotFoundException(nameof(Fund), fundId);
I recieve a 'CompositionDate does not exists' error.
As you can see the CompositionDate property is at the Compositions Level.
When I check the SQL generated I get this in a subquery Select statement :
SELECT f1."CompositionFundId", f1."CompositionDate", f1."AssetId", f1."Amount", a."Id", a."CountryCode", a."Currency", a."FundCompositionDate", a."FundCompositionFundId", a."Isin", a."Name", a."SecurityType", a."Ticker", a."Coupon", a."GicsSector", a."InvestmentCase", a."IpoDate", a."Theme"
FROM "FundCompositionItem" AS f1
INNER JOIN "Asset" AS a ON f1."AssetId" = a."Id"
Those 2 properties a."FundCompositionDate", a."FundCompositionFundId" doesn't exists at the 'Asset' level.
They exists in the parent (at the 'Where' level on the first Include).
I'm using Postgres provider for EFcore. Could this be the issue?
Should I be using the select anonymous type .Select(x => new { Fund = x, Compo = x.Compo.Where(...), etc... }?
I would like to preserve the navigation properties if possible. (accessing assets from compositionItems)
Any help would be much appreciated.
Edit:
Models as requested by Atiyar:
public class Portfolio : AuditableEntity
{
public Guid Id { get; set; }
public Guid Name{ get; set; }
}
public class Fund : Portfolio
{
// Irrelevant properties
public IList<FundComposition> Compositions { get; } = new List<FundComposition>();
}
public class FundComposition
{
public Fund Fund { get; set; }
// Primary key / Foreign key
public Guid FundId { get; set; }
// Primary Key
public DateTime Date { get; set; }
public List<FundCompositionItem> CompositionItems { get; set; } = new();
}
public class FundCompositionItem
{
public FundComposition Composition { get; set; }
// Primary Key
public Guid CompositionFundId { get; set; }
// Primary Key
public DateTime CompositionDate { get; set; }
public Asset Asset { get; set; }
// Primary Key
public Guid AssetId { get; set; }
public double Amount { get; set; }
}
public class Asset : BaseEntity
{
// Primary Key
public Guid Id { get; set; }
public string Name { get; set; }
public string Currency { get; set; }
// more properties
}
In my experience, I've applied the Include() and ThenInclude() first and then applied the any conditional clauses afterwards. I'm also not sure if using Where inside of an include method does what you expect it to.
You can also apply your conditional in the first parameter of .FirstOrDefaultAsync().
var fund = await funds.Where(x => x.Id == fundId)
.Include(f => f.Compositions)
.ThenInclude(c => c.CompositionItems)
.ThenInclude(item => item.Asset)
.FirstOrDefaultAsync(x =>
x.Id == fundId && x.Compositions.Any(compo => compo.Date == compositionDate),
token
)

Fluent Nhibernate error Foreign Key

I know that this issue has some questions about it, but I can't find the right answer, so please let me ask this question to see if someone could give me the right answer
I have the following scheme for my DB (tables an dsimplified to focus on the problem)
Table Project
idProject INT PK
projectName string UNIQUE
numOfItems INT
Table Item
serialNumber integer PK
idProject integer PK, FK (references idProject table Project)
fileName string PK
Table Analysis
serialNumber integer PK, FK (references serialNumber table Item)
dateMeasure Date PK
fileName string PK
I have those tables coded in C# as follows
class Analysis{
public virtual Item serialNum{ get; set; }
public virtual DateTime dateMeasure { get; set; }
public virtual string fileName { get; set; }
public override int GetHashCode(){
return (fileName.GetHashCode() * serialNum.GetHashCode() * dateMeasure.GetHashCode());
}
public override bool Equals(object obj){
if (obj == null || obj.GetType() != GetType()) return false;
Analysis a = (Analysis)obj;
return (a.serialNum == serialNum && a.fileName == fileName && a.dateMeasure == dateMeasure);
}
}
class Item{
public virtual int serialNumber { get; set; }
public virtual Proyecto idProject { get; set; }
public virtual string fileName { get; set; }
public virtual DateTime measureDate { get; set; }
public override int GetHashCode(){
return (fileName.GetHashCode() * serialNumber.GetHashCode() * idProject.GetHashCode());
}
public override bool Equals(object obj)
{
if (obj == null || obj.GetType() != GetType()) return false;
Item i = (Item)obj;
return (i.serialNumber == serialNumber&& i.fileName== fileName&& d.idProject == idProject);
}
}
class Project
{
public virtual int idProject { get; set; }
public virtual string projectName{ get; set; }
public virtual int numItems { get; set; }
}
And the following mappings of the entites
class AnalysisMap: ClassMap<Analysis>
{
public AnalysisMap()
{
CompositeId()
.KeyReference(x=> x.serialNumber)
.KeyProperty(x => x.dateMeasure)
.KeyProperty(x => x.fileName);
}
}
class ItemMap : ClassMap<Item>
{
public ItemMap()
{
CompositeId()
.KeyProperty(x => x.fileName)
.KeyReference(x => x.idProject)
.KeyProperty(x => x.serialNumber);
Map (x=>x.measureDate).Column("dateMeasure").Not.Nullable();
References(x => x.idProject).Column("idProject");
}
}
class ProjectMap : ClassMap<Project>
{
public ProjectMap()
{
Id(x => x.idProject).GeneratedBy.Identity().Column("idProject");
Map(x => x.projectName).Column("projectName").Unique();
Map(x => x.NumItems).Column("numOfItems").Not.Nullable().Default("0");
}
}
So when I tried to open the session with the following code I get the error "Foreign key (FK9CF1483E7BAABE07:Analysis [serialNum])) must have same number of columns as the referenced primary key (Item [fileName, idProject, serialNumber])"
try{
ISessionFactory sf = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory().ShowSql()
.ConnectionString("server=local;Data Source= data_source;Integrated Security=SSPI;"))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Project>()
.AddFromAssemblyOf<Item>()
.AddFromAssemblyOf<Analysis>()).BuildSessionFactory();
ISession session = sf.OpenSession();
lblStatus.Text = "OK";
}
catch (Exception ex){
lblStatus.Text = ex.Message.ToString();
}
So how should I get the mapping in order to make this work??
First you got to understand the error, you said:
Table Analysis serialNumber integer PK, FK (references serialNumber
table Item)
This is wrong, serial number is a FK that references serialNumber, idProject and filename, because the three together forms table Item PK. That's why the error says "...must have same number of columns as the referenced primary key (Item [fileName, idProject, serialNumber])", the primary key of table Item is composed by three fields together and not only by "serialNumber" as you have suggested.
Take a look at this link, it explains how to configure a composite foreign key, this is what you need.
Comment bellow if you need any help.

Entity Framework 6 custom many-to-many child with implicit insert and delete

I have parent object (LoanApplication) with a child (LoanApplicationQualificationTypes) that is a custom many-to-many table. The reason I have a custom is that it has two audit columns that need to be populated (ModifiedBy, ModifiedDate).
To get the children that were added or removed from the child collection to be persisted in the database correctly, I had to explicitly handle.
Below is the code (simplified by removing other properties that were germane to the question).
Parent (part of many-to-many):
[Serializable]
[Table("LoanApplication")]
public class LoanApplication : BaseDomainModelWithId, ILoanApplication
{
[Key]
[Column("LoanApplicationId")]
public override int? Id { get; set; }
[ForeignKey("LoanApplicationId")]
public virtual ICollection<LoanApplicationQualificationTypes> LoanApplicationQualificationTypes { get; set; }
IReadOnlyCollection<ILoanApplicationQualificationTypes> ILoanApplication.LoanApplicationQualificationTypes
{
get
{
var loanApplicationQualificationTypes = new List<ILoanApplicationQualificationTypes>();
if (LoanApplicationQualificationTypes == null) return loanApplicationQualificationTypes;
loanApplicationQualificationTypes.AddRange(LoanApplicationQualificationTypes);
return loanApplicationQualificationTypes.AsReadOnly();
}
set
{
foreach (var item in value)
{
LoanApplicationQualificationTypes.Add((LoanApplicationQualificationTypes)item);
}
}
}
public LoanApplication() : base()
{
LoanApplicationQualificationTypes = new List<LoanApplicationQualificationTypes>();
}
}
public interface ILoanApplication : IDomainModel, ILoanApplicationBase, IKeyIntId
{
IReadOnlyCollection<ILoanApplicationQualificationTypes> LoanApplicationQualificationTypes { get; set; }
}
Object part of many-to-many:
[Serializable]
[Table("QualificationType")]
public class QualificationType : IQualificationType
{
[Key]
[Column("QualificationTypeId")]
public override int? Id { get; set; }
[Required]
public string TypeName { get; set; }
[Required]
public bool IsActive { get; set; }
public virtual string ModifiedBy { get; set; }
public virtual DateTimeOffset? ModifiedDate { get; set; }
public QualificationType() : { }
}
Custom Many-to-Many:
[Serializable]
[Table("LoanApplicationQualificationTypes")]
public class LoanApplicationQualificationTypes : ILoanApplicationQualificationTypes
{
[Key]
[Column(Order = 1)]
public int? LoanApplicationId { get; set; }
[ForeignKey("LoanApplicationId")]
public virtual LoanApplication LoanApplication { get; set; }
ILoanApplication ILoanApplicationQualificationTypes.LoanApplication
{
get
{
return this.LoanApplication;
}
set
{
this.LoanApplication = (LoanApplication)value;
}
}
[Required]
[Key]
[Column(Order = 2)]
public int QualificationTypeId { get; set; }
[ForeignKey("QualificationTypeId")]
public virtual QualificationType QualificationType { get; set; }
IQualificationType ILoanApplicationQualificationTypes.QualificationType
{
get
{
return this.QualificationType;
}
set
{
this.QualificationType = (QualificationType)value;
}
}
public virtual string ModifiedBy { get; set; }
public virtual DateTimeOffset? ModifiedDate { get; set; }
public LoanApplicationQualificationTypes() { }
}
Update method in LoanApplication Repository:
public bool Update(ILoanApplication entity)
{
using (var db = new MainContext())
{
entity.ModifiedDate = DateTime.UtcNow;
entity.ModifiedBy = UserOrProcessName;
// Add / Remove LoanApplicationQualificationTypes and populate audit columns
if (entity.LoanApplicationQualificationTypes?.Count > 0)
{
var existingItems = db.LoanApplicationQualificationTypes.Where(q => q.LoanApplicationId == entity.Id.Value).ToList();
var newItems = entity.LoanApplicationQualificationTypes.Where(q => existingItems.All(e => e.QualificationTypeId != q.QualificationTypeId));
var deletedItems = existingItems.Where(q => entity.LoanApplicationQualificationTypes.All(e => e.QualificationTypeId != q.QualificationTypeId));
foreach (var newItem in newItems)
{
newItem.ModifiedBy = UserOrProcessName;
newItem.ModifiedDate = DateTime.UtcNow;
db.LoanApplicationQualificationTypes.Add((LoanApplicationQualificationTypes)newItem);
}
foreach (var deletedItem in deletedItems)
{
db.LoanApplicationQualificationTypes.Remove((LoanApplicationQualificationTypes)deletedItem);
}
// Need to clear to avoid duplicate objects
((LoanApplication)entity).LoanApplicationQualificationTypes.Clear();
}
db.Entry(entity).State = EntityState.Modified;
db.SaveChanges();
}
return true;
}
Is there a way implement the Update without the explicitly handling adds/updates?
The way I understand it, the question is how to apply the (potential) modifications to the link table without explicitly detecting added/removed links. Also I assume the other part of the link must exist.
It's possible with the following sequence of operations:
First load the actual entity from the database into context, including the links:
var dbEntity = db.LoanApplication
.Include(e => e.LoanApplicationQualificationTypes)
.FirstOrDefault(e => e.Id == entity.Id);
This will allow change tracker to determine the correct add/update/delete link operations for you later.
Then apply the primitive master data changes:
db.Entry(dbEntity).CurrentValues.SetValues(entity);
dbEntity.ModifiedDate = DateTime.UtcNow;
dbEntity.ModifiedBy = UserOrProcessName;
Finally, replace the links with the ones from the incoming entity. To avoid navigation property references pointing to different objects (and in particular to prevent EF trying to create the new records for the other side objects of the relation), do not use directly the incoming objects, but create stub objects with only FK properties set:
dbEntity.LoanApplicationQualificationTypes = entity.LoanApplicationQualificationTypes
.Select(e => new LoanApplicationQualificationTypes
{
LoanApplicationId = e.LoanApplicationId,
QualificationTypeId = e.QualificationTypeId,
ModifiedDate = DateTime.UtcNow,
ModifiedBy = UserOrProcessName,
})
.ToList();
And that's it. At this point the change tracker has all the necessary information to produce the correct commands when you call db.SaveChanges().
One thing to mention. If you look at db.ChangeTracker.Entries at this point, you'll probably notice that all the old links are marked as Deleted, all the incoming as Added and there are no Modified entries. Don't worry. EF is smart enough and will convert Deleted + Added pairs with the same PK to single update commands.
The whole method:
public bool Update(ILoanApplication entity)
{
using (var db = new MainContext())
{
var dbEntity = db.LoanApplication
.Include(e => e.LoanApplicationQualificationTypes)
.FirstOrDefault(e => e.Id == entity.Id);
if (dbEntity == null) return false;
db.Entry(dbEntity).CurrentValues.SetValues(entity);
dbEntity.ModifiedDate = DateTime.UtcNow;
dbEntity.ModifiedBy = UserOrProcessName;
dbEntity.LoanApplicationQualificationTypes = entity.LoanApplicationQualificationTypes
.Select(e => new LoanApplicationQualificationTypes
{
LoanApplicationId = e.LoanApplicationId,
QualificationTypeId = e.QualificationTypeId,
ModifiedDate = DateTime.UtcNow,
ModifiedBy = UserOrProcessName,
})
.ToList();
db.SaveChanges();
return true;
}
}

Fluent NHibernate Mapping Help static view referencing with compositeID

Afternoon all. I'm trying to create a mapping for a flight segment database where at the bottom of the mapping tree a FlightSegment references an origin and destination table with a compositeID consisting of a three letter code and a Boolean determining whether the code is or isn't a city.
Below are the relevant simplified class structures:
public class GTIFlightSegment
{
public virtual int ID { get; protected set; }
public virtual GTIOriginAirport Origin { get; set; }
public virtual GTIDestinationAirport Destination { get; set; }
public virtual GTIFlightSegmentGroup Parent { get; set; }
}
public class GTIAirport
{
public virtual string Code { get; set; }
public virtual string Name { get; set; }
public virtual string City { get; set; }
public virtual string CountryCode { get; set; }
public virtual GTIGeoCode GeoCode {get; set; }
public virtual string Terminal { get; set; }
public virtual bool IsCity { get; set; }
public GTIAirport()
{
GeoCode = new GTIGeoCode();
IsCity = false;
}
public override bool Equals(object obj)
{
var other = obj as GTIAirport;
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return this.Code == other.Code && this.IsCity == other.IsCity;
}
public override int GetHashCode()
{
unchecked
{
int hash = GetType().GetHashCode();
hash = (hash * 31) ^ Code.GetHashCode();
hash = (hash * 31) ^ IsCity.GetHashCode();
return hash;
}
}
}
public class GTIOriginAirport : GTIAirport
{
public virtual GTIFlightSegment Parent { get; set; }
public GTIOriginAirport() : base()
{
}
}
public class GTIDestinationAirport : GTIAirport
{
public virtual GTIFlightSegment Parent { get; set; }
public GTIDestinationAirport() : base()
{
}
}
And below are the mappings I've created so far for the objects:
public class GTIFlightSegmentMap : ClassMap<GTIFlightSegment>
{
public GTIFlightSegmentMap()
{
Id(x => x.ID);
References(x => x.Destination).Columns(new string[] { "DestinationCODE", "DestinationIsCity" }).Cascade.All();
References(x => x.Origin).Columns(new string[] { "OriginCODE", "OriginIsCity"}).Cascade.All();
References(x => x.Parent).Not.Nullable();
}
}
public class GTIAirportMap : ClassMap<GTIAirport>
{
public GTIAirportMap()
{
Table("GTIAirport");
ReadOnly();
CompositeId()
.KeyProperty(x => x.Code, "CODE")
.KeyProperty(x => x.IsCity, "isCity");
Map(x => x.Name).Column("Airport");
Map(x => x.City);
Map(x => x.CountryCode);
Component(x => x.GeoCode, m =>
{
m.Map(x => x.Latitude);
m.Map(x => x.Longitude);
});
}
}
public class GTIOriginAirportMap : SubclassMap<GTIOriginAirport>
{
public GTIOriginAirportMap()
{
KeyColumn("CODE");
KeyColumn("isCity");
HasOne(x => x.Parent).PropertyRef(x => x.Origin);
}
}
public class GTIDestinationAirportMap : SubclassMap<GTIDestinationAirport>
{
public GTIDestinationAirportMap()
{
KeyColumn("CODE");
KeyColumn("isCity");
HasOne(x => x.Parent).PropertyRef(x => x.Origin);
}
}
What I'm trying to achieve is that when a FlightSegment is created in the system it will store the DestinationIsCity/DestinationCode and the OriginIsCity/OriginCode in the flight segments table but not try and update the GTIAirport reference table view. But when the objects are retrieved from the database with a query over, the rich information from the GTIAirport reference table will be fetched.
Or possibly have the Code and IsCity in the DepartureAirport and OriginAirport tables respectively with a ID reference back to the flight segment.
No matter what connotations I'm trying I'm hitting a wall of some kind. Basically I've got myself in a bit of a mess. I've flipped the relationships between the Airport and segment, swapping out to only references. Cascaded, not cascaded.
Common Errors being encountered whilst playing with the mappings:
1) The UPDATE statement conflicted with the FOREIGN KEY constraint
2) {"broken column mapping for: Destination.id of: GilesSabreConnection.Profiling.Model.BookingEngineModel.Model.GTIFlightSegment, type component[Code,IsCity] expects 2 columns, but 1 were mapped"}
3) {"Foreign key (FK582A9C81E6C3913B:GTIFlightSegment [Destination_id])) must have same number of columns as the referenced primary key (GTIDestinationAirport [CODE, isCity])"}
In case it's needed the fluent config is:
return Fluently.Configure().Database(MsSqlConfiguration.MsSql2012.ConnectionString(#"Data Source=Dev2;Initial Catalog=Sandbox;Integrated Security=True")).Mappings(m => m.FluentMappings.AddFromAssemblyOf<Profile>()).ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(true, true)).BuildSessionFactory();
Any help to point me in the right direction from the database and fluent gurus would be greatly appreciated.
Many thanks in advance.
Realised that by setting the GTIAirport Map as readonly I was inherently making the Destination and Origin maps as readonly so the mapping could never work no matter how I was configuring the relationships.
So I've split the static reference database of detailed airport info accessed via the View named GTIAiport into two, so the Boolean in the composite key is no longer required. I now have the flight segment mapping simply logging the Airport origin/destination codes within the flight segment table using the Component class in fluent. In the Airport class itself now have a function to independently fetch the rich data from the static reference database table on request using the stored Airport code.
Couldn't figure out a way to do this with fluent. Interested to know if there is a way.
public GTIFlightSegmentMap()
{
Id(x => x.ID);
Component(x => x.Destination, m =>
{
m.Map(y => y.Code).Column("DestinationCode");
});
Component(x => x.Origin, m =>
{
m.Map(y => y.Code).Column("OriginCode");
});
;
References(x => x.Parent).Not.Nullable();
}
}

Pocos - The relationship could not be changed because one or more of the foreign-key properties is non-nullable

I have three tables, and they are user, user_details and user_acls. The user table is master table, and the other two are children tables that store information related to the user. The foreign key in the latter two tables is user_id, and user_id is the primary key on the user table.
Using entity framework and pocos I have set up the following class structure, and I will only do it for one of my child tables, as once I have this resolved I should be able to apply the same logic to my other user table.
User Class
namespace myclass.Core.Domain.Customers
{
public partial class User : BaseEntity
{
private ICollection<UsersSitesAcl> _usersSitesAcls;
private ICollection<CompaniesAcl> _companiesAcls;
private ICollection<UserDetails> _userDetails;
public virtual Guid userguid { get; set; }
public virtual string first_name { get; set; }
public virtual string last_name { get; set; }
public virtual string email_address { get; set; }
public virtual System.DateTime? activated_date { get; set; }
public virtual string pwrd { get; set; }
public virtual string loginname { get; set; }
public virtual string title { get; set; }
public virtual string suffix { get; set; }
public virtual string secretquestion { get; set; }
public virtual string secretanswer { get; set; }
public virtual long? birthyear { get; set; }
public virtual string last_four_ssn { get; set; }
public virtual bool? suspended_yn { get; set; }
public virtual string account_status { get; set; }
public virtual ICollection<UsersSitesAcl> usersSitesAcls
{
get
{
var sitesAcls = new List<UsersSitesAcl>();
if (_usersSitesAcls != null)
{
var query = from usa in _usersSitesAcls
where usa.active_yn
select usa;
sitesAcls = query.ToList();
}
return sitesAcls;
}
protected set { _usersSitesAcls = value; }
}
public virtual ICollection<CompaniesAcl> companiesAcls
{
get
{
var companyAcls = new List<CompaniesAcl>();
if (_companiesAcls != null)
{
var query = from ca in _companiesAcls
where ca.active_yn
select ca;
companyAcls = query.ToList();
}
return companyAcls;
}
protected set { _companiesAcls = value; }
}
public virtual ICollection<UserDetails> userDetails
{
get
{
var userDetails = new List<UserDetails>();
if (_userDetails != null)
{
userDetails = (from ud in _userDetails where ud.active_yn select ud).ToList();
}
return userDetails;
}
set { _userDetails = value; }
}
}
}
User Details Class
namespace myclass.Core.Domain.Customers
{
public partial class UserDetails : BaseEntity
{
private User _updatedByUser;
public virtual long user_id { get; set; }
public virtual string primary_specialty { get; set; }
public virtual string secondary_specialty { get; set; }
public virtual string npi { get; set; }
public virtual string state_licence_number { get; set; }
public virtual string issuing_state { get; set; }
public virtual string dea_number { get; set; }
public virtual string dea_schedule1 { get; set; }
public virtual string dea_schedule2 { get; set; }
public virtual string dea_schedule3 { get; set; }
public virtual string dea_schedule4 { get; set; }
public virtual string dea_schedule5 { get; set; }
public virtual string dea_expire_date { get; set; }
public virtual string state_licence_expire_date { get; set; }
public virtual string provider_rights { get; set; }
public virtual long updated_by_user_id { get; set; }
public virtual User updatedByUser
{
get { return _updatedByUser; }
protected set
{
_updatedByUser = value;
updated_by_user_id = _updatedByUser.Id;
}
}
}
}
For my mapping I have the following structure for both the user and user details
UserMap
namespace myclass.Data.Mappings.Domains.Customers
{
public partial class UserMap : EntityTypeConfiguration<User>
{
public UserMap()
{
this.ToTable("users");
this.HasKey(u => u.Id);
this.Property(u => u.Id).HasColumnName("user_id");
this.Property(u => u.userguid).IsRequired();
this.Property(u => u.first_name).HasMaxLength(50).IsRequired();
this.Property(u => u.last_name).HasMaxLength(50).IsRequired();
this.Property(u => u.pwrd).HasMaxLength(100).IsRequired();
this.Property(u => u.email_address).HasMaxLength(100).IsRequired();
this.Property(u => u.loginname).HasMaxLength(50).IsRequired();
this.Property(u => u.activated_date).IsOptional();
this.Property(u => u.create_date).IsRequired();
this.Property(u => u.active_yn).IsRequired();
this.Property(u => u.title).IsOptional();
this.Property(u => u.suffix).IsOptional();
this.Property(u => u.last_four_ssn).IsOptional();
this.Property(u => u.secretquestion).IsOptional();
this.Property(u => u.secretanswer).IsOptional();
this.Property(u => u.birthyear).IsOptional();
this.Property(u => u.account_status).IsOptional();
this.Property(u => u.suspended_yn).IsOptional();
this.HasMany(u => u.userDetails).WithRequired().HasForeignKey(ud => ud.user_id);
this.HasMany(u => u.usersSitesAcls).WithRequired().HasForeignKey(usa => usa.user_id);
this.HasMany(u => u.companiesAcls).WithRequired().HasForeignKey(ca => ca.user_id);
}
}
}
UserDetails Map
enter code here
namespace myclass.Data.Mappings.Domains.Customers
{
public partial class UserDetailsMap:EntityTypeConfiguration<UserDetails>
{
public UserDetailsMap()
{
this.ToTable("user_details");
this.HasKey(ud => ud.Id);
this.Property(ud => ud.Id).HasColumnName("user_detail_id");
this.Property(ud => ud.user_id).IsRequired();
this.Property(ud => ud.primary_specialty).HasColumnName("primary_speciality").IsOptional();
this.Property(ud => ud.secondary_specialty).IsOptional();
this.Property(ud => ud.npi).IsOptional();
this.Property(ud => ud.state_licence_number).HasColumnName("StateLicenseNumber").IsOptional();
this.Property(ud => ud.issuing_state).HasColumnName("IssuingState").IsOptional();
this.Property(ud => ud.dea_number).HasColumnName("DEANumber").IsOptional();
this.Property(ud => ud.dea_schedule1).HasColumnName("DEASchedule1").IsOptional();
this.Property(ud => ud.dea_schedule2).HasColumnName("DEASchedule2").IsOptional();
this.Property(ud => ud.dea_schedule3).HasColumnName("DEASchedule3").IsOptional();
this.Property(ud => ud.dea_schedule4).HasColumnName("DEASchedule4").IsOptional();
this.Property(ud => ud.dea_schedule5).HasColumnName("DEASchedule5").IsOptional();
this.Property(ud => ud.dea_expire_date).HasColumnName("DeaExpireDate").IsOptional();
this.Property(ud => ud.state_licence_expire_date).HasColumnName("StateLicenseExpireDate").IsOptional();
this.Property(ud => ud.provider_rights).HasColumnName("ProviderRights").IsOptional();
this.Property(ud => ud.active_yn).IsRequired();
this.Property(ud => ud.create_date).IsRequired();
this.Property(ud => ud.updated_by_user_id).IsRequired();
this.HasRequired(ud => ud.updatedByUser).WithMany().HasForeignKey(ud => ud.updated_by_user_id);
//
}
}
}
First of I am adding a new record, and I set the contents of the User class (bar the User_details and user_acls, and it goes out and creates the record for the user without any issues.
However, when I try to do the same for the user_details and user_acls, I have not had any success, and have tried to adding the user_details as item to the property on the user table without any success.
And have even tried saving it out as a separate record, where I store the user record, retrieve the user_id of the new record added. Then create a new object for user_details and user_acls, and try to a save on that and I end up with the following message:
What do I need to do to make this work, I have tried everything I know and no success. So any help ye can give would be appreciated. Thanks.
Blockquote
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.
That's a lot of code to read but I think the issue is that you're modifying the entity state while it's not being tracked by the entity framework.
Try something like
db.Entry(currentyUser).State = EntityState.Modified;
before saving changes.
You show a lot of code but not, I suspect, the code in question.i.e. how you create a user and attach user detail and then how you save those objects.
A simple test can be to use
this.Ignore(ud=>ud.User)
as part of your user detail mapping.
If this works you can be fairly certain the issue is that the User associated with the UserDetail has a state other than Unchanged. What should be happening is that the User should be getting saved first and then having its state set to Unchanged and then the User Details should be getting saved.
Because you dont show your attach and save code I can only speculate but I would bet money that the state of User is not Unchanged, probably Added or some custom state like Inactive if you are using disconnected entities.
Therefore EF tries to recreate the User object ( if state == Added) and then reassign it to the UserDetails which somehow orphans the User Details by nulling the previous User object association and then attaching the new.
I'm still working through the docs on exactly how this happens but my own experience just this last half hour has been that the parent / foreign key object had a state other than Unchanged. So EF is differentiating the reference of User in your User Details object from what it has in it's DBSets. Thus the null reference.
I understand you are saving a new User and their details at the same time but look carefully at your code as to how you do that. Most likely there is some kind of "User swap" going on whereby the User you save first is not actually the User that's attached to UserDetails when trying to save them. Are you doing any Linq ToList() type "stuff" and inadvertently switching references to an non Unchanged User object.
In my case it was because I was creating a "stub" object in code of an FK object in the db and I overlooked settings it's state to Unchanged before associating it with my detail object.
Sorry that my answer is a bit of a waffle but Im just working it through it now. When I understand it more consisely I will update this answer.
I figured this out and it had to do with the way I was accessing the user object using autofac, in the end it was something on my end.

Categories

Resources