Inserting Entities in OData with required Foreign Keys - c#

EDIT-2: After hours of research and almost every odata related link on google turning purple, I found out that the concept of 'deep-inserts' (link) exists in the OData specification. So after all, what I'm doing should work, even without the links. Does anyone know how to enable this on the Microsoft OData client? Are there any other OData clients out there that support that concept?
EDIT: Maybe this is the wrong approach, so please tell me if I'm doing it totally wrong. Not being able to save is really blocking our progress!
I have an issue with OData v3. I have a class Associate that has a required Address. When I try to POST a new Associate, it fails due to the Address property being null (EF6 throws DbUpdateException with foreign key violation). My Associate class looks like this:
public class Associate
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; }
[Required, StringLength(50)]
public string Role { get; set; }
public bool IsMailReceiver { get; set; }
public bool IsLegalRepresentative { get; set; }
[ForeignKey("AddressId")]
public virtual Address Address { get; set; }
public int AddressId { get; set; }
}
I use the Microsoft OData client, and try to add the associate in the following way:
var associate = new Associate { /* ... */ };
context.AddObject("Associates", associate);
context.AddObject("Addresses", associate.Address);
/* UI fills associate data */
context.SetLink(associate, "Address", associate.Address);
context.UpdateObject(associate);
context.UpdateObject(associate.Address);
/* at this point the associate has the address set! */
context.SaveChanges(); // << Exception
On the server, in the controller, the Associate arrives without the foreign key, however. When I inspect the POST request with Fiddler, I see why:
{
"odata.type" : "xxx.Data.Entities.Associate",
"AddressId" : 0,
"Id" : 0,
"IsLegalRepresentative" : false,
"IsMailReceiver" : false,
"Name" : "John Doe",
"Role" : "Father"
}
The address is not transmitted, even though the generated class on the client has an Address property.
How can i solve this problem?

I too could not find any information about this - it really feels like an issue in OData. Here is how I managed to get it to work.
Define the foreign key explicitly
class Student {
public int TeacherId { get; set; }
[Required, ForeignKey("TeacherId")]
public virtual Teacher Teacher { get; set; }
}
When performing the insert, fetch the related record and fix the model state:
public IHttpActionResult Post(Student student)
{
student.Teacher = this.db.Teacher.FirstOrDefault(i => i.TeacherId == student.TeacherId);
if (student.Teacher != null)
{
this.ModelState.Remove("student.Teacher");
}
if (!this.ModelState.IsValid)
{
return this.BadRequest(this.ModelState);
}
}
So from then on to post a Student, you ignore the Teacher field and just post with TeacherId.
I haven't tested this with the OData client, but I can't think of why this wouldn't work. You will just have to use the Id field rather than the object.

Basically when you create the object
var associate = new Associate { /* ... */ };
It is not inserted into the database. It is created in the memory. When you call
context.SaveChanges();
It will be saved in the database. At this point database validation happens and key's are generated. Assuming your Id is unique identifier, that is generated in the datebase, note that in order for it to get updated value back from the database you need to have StoreGeneratedPattern set to Identity from Entity model view.
If this is not done your local context and database context no longer match. If you where to use that object with reference to something else it would fail.
I assume something like this would work:
Address address = new Address{ City = "Tallinn" /*etc*/};
context.SaveChanges();
//At this point Address will be in database context and has Id
associate = new Associate {
name = "Margus",
role = "Admin",
receiver = true,
representative = true,
AddressId = address.id
};
context.SaveChanges();

There is no solution to this. I will roll my own context with a notion of change sets that works with web-api. I will put it on github, when I'm done.

The only way addlink and setlink work is if the foreign key is nullable and you ahvbe to create a postput function call create link see here

I came across this and I can confirm that it is indeed a problem with the OData client (proxy), although I haven't found any references about it.
I managed to fix it using a workaround, which is not 100% perfect but works for me. Here is the code and I will explain about its shortcomings.
public static class DataServiceContextExtensions
{
public static int PostChanges(this DataServiceContext context)
{
using (var client = new WebClient())
{
client.Credentials = context.Credentials;
client.Headers[HttpRequestHeader.ContentType] = "application/json";
var entities = context.Entities.Where(x => x.State == EntityStates.Added);
foreach (var descriptor in entities)
{
var url = $"{context.BaseUri}{descriptor.Entity.GetType().Name}";
var data = JsonSerializer.Serialize(descriptor.Entity);
var response = client.UploadString(url, data);
context.ChangeState(descriptor.Entity, EntityStates.Detached);
}
return entities.Count();
}
}
}
As you can see, I am using an extension method over DataServiceContext where I iterate through all the entities stored in the change tracker which are marked as added, and then I use a WebClient to POST a JSON serialized version of them to the OData endpoint, using any credentials that the proxy might have.
Problems:
First, it only deals with added entities, I'm OK with that, but others may need a more comprehensive solution. The only way I see is to replace WebClient with some other client that can do arbitrary HTTP verbs.
Second, it doesn't hydrate the entities with the generated primary key. Again, I don't need it in my case, but this one will be difficult to solve, as OData does not seem to return it on the result of UploadString.
Third, all URLs are always, by convention, assumed to be composed as BaseUri + entity type (e.g., https://foo.bar/MyEntity). This may or may not always be the case, not 100% sure, again, works in my case.

Related

How to partly update a database record?

I am struggling to implement a simple 'update profile' functionality(learning purposes). I simply want to be able not to update the profile image everytime I update a give profile. When the picture is there and some other part of the profile is update I want the picture to stay the same.
I came up with the following code for this :
Controller :
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "UserDetailsId,ImageData,FirstName,LastName,UserAddress,UserCountry,UserPostalCode,UserPhoneNumber,CompanyId,identtyUserId")] UserDetails userDetails, HttpPostedFileBase UploadImage)
{
if (ModelState.IsValid)
{
if (UploadImage!=null) {
byte[] buf = new byte[UploadImage.ContentLength];
UploadImage.InputStream.Read(buf, 0, buf.Length);
userDetails.ImageData = buf;
}
else {
var userFromDb = db.UsersDetails.Where(u => u.identtyUserId == userDetails.identtyUserId).First();//i am getting the old user data
userDetails.ImageData = userFromDb.ImageData; //saving the image to the modified state
}
db.Entry(userDetails).State = EntityState.Modified;//error here
db.SaveChanges();
return RedirectToAction("Index");
}
//ViewBag.CompanyId = new SelectList(db.Companies, "CompanyId", "CompanyName", userDetails.CompanyId);
return View(userDetails);
The error I am getting on this row db.Entry(userDetails).State = EntityState.Modified; is the following :
Attaching an entity of type 'eksp.Models.UserDetails' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
The model :
public class UserDetails
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserDetailsId { get; set; }
public byte[] ImageData { get; set; }
[NotMapped]
public HttpPostedFileBase UploadImage { get; set; }
[NotMapped]
public string ImageBase64 => System.Convert.ToBase64String(ImageData);
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserPhoneNumber { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
public string identtyUserId { get; set; }
public virtual ICollection<WorkRolesUsersDetails> WorkRolesUsersDetails { get; set; }
Although it may looke pretty self explenatory for me it is not clear this is happening?
Can somebody guide me how to achieve what I want to achieve?
Thanks!
When you update an entity, you should map the posted values onto an instance pulled from the database, rather than trying to directly save the instance created from the post data. This is yet another reason to avoid using Bind as it confuses the issue and makes developers who don't know better think it's okay to directly save the entity created from the post data. It's not, and never is.
Instead, use something like UserDetailsId to lookup the entity:
var userDetails = db.UserDetails.Find(model.UserDetailsId);
Where model is the parameter from your action. Then, you can map over the posted values onto your entity:
userDetails.FirstName = model.FirstName;
// etc.
Finally, save usersDetails, which is now the version from the database, with all the original data on the entity, modified to the posted data, where appropriate.
Now, given that you need to do this mapping over of the posted data anyways, go a step further can create a view model with just the properties you need to allow the user to modify. You can then post to that, instead of using Bind. Really, Bind is just awful. It's one of those things Microsoft hastily adds because they think it solves one problem, and it actually ends up causing ten other problems.
You can retrieve data entity from the db in any case and update it with the details coming as part of the Post Model and Save that data entity back to the db.
var userFromDb = db.UsersDetails.Where(u => u.identtyUserId == userDetails.identtyUserId).First();
if (UploadImage!=null)
{
byte[] buf = new byte[UploadImage.ContentLength];
UploadImage.InputStream.Read(buf, 0, buf.Length);
userFromDb.ImageData = buf;
}
userFromDb.FirstName = userDetails.FirstName;
userFromDb.LastName = userDetails.LastName;
userFromDb.UserAddress = userDetails.UserAddress;
userFromDb.UserCountry = userDetails.UserCountry;
userFromDb.UserPostalCode = userDetails.UserPostalCode;
userFromDb.UserPhoneNumber = userDetails.PhoneNumber;
userFromDb.CompanyId = userDetails.CompanyId;
db.SaveChanges();
This should help you to achieve the feature you want.

Fail to remove ef entity with parent property required

I am not fantastic with EF so maybe it's an easy one.
I Have
public void DeleteLicense(int licenseId)
{
var entityToDelete = context.Licenses.Find(licenseId);
context.Licenses.Remove(entityToDelete);
}
I have checked that it finds correct license, and context is a ninject (one per request) DbContext,
But I get a weird error when I call SaveChanges() on the context after running the function above. I get: "The CustomerName field is required."
Now this is weird because CustomerName is in Account (not Licence) they are linked, but still. So here follows some more:
My Account entity
[Required]
public String CustomerName { get; set; }
public virtual ICollection<License> Licenses { get; set; }
...
My License entity
public virtual Account Account { get; set; }
...
My fluent setup
modelBuilder.Entity<Account>().HasMany(x => x.Licenses)
.WithRequired(x => x.Account).WillCascadeOnDelete(false);
I don't understand, because even if there is a failing restraint then why missing CustomerName. I don't touch CustomerName when I delete a license and the CustomerName is set since before.
Update
So here is some more details from the code. The full execution path as far as I can see is
DeleteLicenseAPI below takes the call, the ID is correct, it passes over to a private function.
The private function calls the DeleteLicense shown close to the top of the question.
The Commit() only calls context.SaveChanges();
public ActionResult DeleteLicenseAPI(int licenseId)
{
if (DeleteLicense(licenseId))
{
return Content("ok");
}
return Content("[[[Failed to delete license]]]");
}
private bool DeleteLicense(int licenseId)
{
//todo: sort out busniess rules for delete, is cascaded?
_accountRepository.DeleteLicense(licenseId);
_accountRepository.Commit();
return true;
}
The _accountRepository looks like this
public class EFAccountRepository : EntityFrameworkRepository<Account>
, IAccountRepository
public EFAccountRepository(EvercateContext context) : base(context)
{
}
And here is the code in Ninject that sets it all up
kernel.Bind<EvercateContext>()
.To<EvercateContext>()
.InRequestScope()
.WithConstructorArgument("connectionStringOrName", "EvercateConnection");
kernel.Bind<IAccountRepository>().To<EFAccountRepository>();
So even tho I use Unit of Work as far as I can see (and it shouldn't) nothing else is called in this request before running SaveChanges.
Is there any way to see what a DbContext will do on SaveChanges, without actually running the method (as it throws DbEntityValidationException)
I can imagine that this weird exception could occur if you are initializing the Account navigation property in the License constructor like so:
public License
{
Account = new Account();
}
The flow when you call...
var entityToDelete = context.Licenses.Find(licenseId);
context.Licenses.Remove(entityToDelete);
...is then probably:
License entity gets loaded (without navigation property Account) and attached to the context (state Unchanged)
The constructor sets the Account navigation property, but it doesn't get attached (state Detached)
When you call Remove for the License entity DetectChanges is called internally by EF. It detects that License.Account is refering to a detached entity and attaches it to the context (in state Added). The state of the License is changed to Deleted.
When you call SaveChanges the change tracker finds two entities: The License in state Deleted and the Account in state Added.
Validation runs and finds that the required property CustomerName for the entity Account that is supposed to be inserted into the database is null (because only the default constructor of Account is called).
The validation exception is thrown.
I'm not sure if the details are right but something like that is probably happening.
In any case you should delete the Account = new Account(); from the License constructor and also check if you initialize other reference navigation properties in entity constructors in your codebase as well. (Initializing empty navigation collections is OK.) This is a common source of notoriously strange problems that are difficult to find and understand.
I tried overriding SaveChanges as recommended.
When I did I found a License about to be deleted (as it should) but I also found an Account about to be created.
I changed the DeleteLicense as displayed below.
public void DeleteLicense(int licenseId)
{
var entityToDelete = context.Licenses.Find(licenseId);
entityToDelete.Account = null;
context.Licenses.Remove(entityToDelete);
}
And right away the code works. The License is removed and the account is still there, but no new account is created.
But why, I do not understand why at all.
Is it something in the relation i set with fluent api?
In my case this happened because my entity had a [Required] property that was of type int? which made it nullable. While inspecting the model that came back from the db I saw the property had a value but the entity that ended up being saved to the database had that value stripped during SaveChanges for some reason. When I switched the value to the expected int type all worked just fine. :shrug:
I had a similar issue and for me, it looked like I hadn't correctly established the relationship between Parent and Child in their respective classes.
My fix was to add the attributes specified below to the Child class, for the property that represented its Parent's Id
public class Child
{
[Key, Column(Order = 1)]
public string Id { get; set; }
[Key, ForeignKey("Parent"), Column(Order = 2)] // adding this line fixed things for me
public string ParentId {get; set;}
}
public class Parent
{
[Key, Column(Order = 1)]
public string Id { get; set; }
...
public virtual ICollection<Child> Children{ get; set; }
}

Updating related data using MVC 4 and Entity Framework?

So, I have a problem in save data which contains related entities, when I save it a new relation blank is created.
Exemple:
Entities:
public class Project
{
public int Id { get; set; }
public int Code{ get; set; }
public string Description{ get; set; }
public virtual Client Client { get; set; }
}
public class Client
{
public int Id { get; set; }
public int Code { get; set; }
public string Name { get; set; }
}
The Controller GET:
public ActionResult Create()
{
PopulateDropDownClienteList(String.Empty); //Returns to ViewBag to create a combobox .in view
return View();
}
The View:
#Html.DropDownListFor(m => m.Client.Id, new SelectList(ViewBag.Client_Id, "Id", "Name"), new { Name = "Client.Id" });
The Controller POST:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(string command, Project project)
{
try
{
if (ModelState.IsValid)
{
projectRepository = new ProjeRepository();
Project pro = projectRepository.ReturnByCode(project.Code);
if (pro == null)
projectRepository.Save(project);
else
projectRepository.Update(project);
PopulateDropDownClienteList(String.Empty);
Return View();
}
else
{
return View(project);
}
}
catch (Exception ex)
{
return View();
}
}
So when I save the data, the client is not associated with the project. just creating a new blank Client.
You Project Save code is not updating the entity, it is ADDING a new one all the time.
You should have update logic similar to following grounds -
To Add new FK Entry and associate it with parent record -
var entity = entities.Students.Where(p => p.Id == "2").First();
entity.StudentContact = new StudentContact() { Contact = "xyz", Id = "2" };
entities.Students.Attach(entity);
var entry = entities.Entry(entity);
// other changed properties
entities.SaveChanges();
To update a FK record with new details -
var entity = entities.Students.FirstOrDefault();
entity.StudentContact.Contact = "ABC";
entities.Students.Attach(entity);
var entry = entities.Entry(entity);
entry.Property(e => e.StudentContact.Contact).IsModified = true;
// other changed properties
entities.SaveChanges();
The above code, I have a Student records which has FK relationship with StudentContacts. I updated Contact information of a student and then updated it to database using ATTACH.
You've got a number of issues here, so let me break them down.
First and foremost, do not ever catch Exception (at least without throwing it again). There's two very important things about using try...catch blocks: you should only wrap the code where you're expecting an exception (not nearly your entire method as you've done here), and you should only catch the specific exception you're expecting (not the base type Exception). When you catch Exception, any and every exception that could possibly be generated from your code will be caught, and in this case, simply discarded, which means you really will never know if this code works at all.
Second, you have a fine method that generates a dropdown list of choices, but never store the user's selection anywhere meaningful. To understand why, you need to stop and think about what's happening here. An HTML select element has a string value and a string text or label component. It does not support passing full objects back and forth. I can't see what your PopulateDropDownClienteList method does, but what it should be doing is creating an IEnumerable<SelectListItem>, where each item gets its Text property set to whatever you want displayed and its Value property to the PK of the Client. However, once you have that, you need some property on Project to post back to. Your virtual Client won't work as that needs a full Client instance, which your form will never have. So, you have two choices:
Implement a view model to feed to the view (and accept in the post). In that view model, in addition to all other editable fields, you'll include something like ClientId which will be an int type, and you'll bind this to your drop down list. Once you're in your post method, you map all the posted values to your project instance, and then use the ClientId to look up a client from the database. You then set the resulting client as the value for your Client property and save as usual.
You alter your database a bit. When you just specify a virtual, Entity Framework smartly creates a foreign key and a column to hold that relationship for you behind the scenes. That's great, but in situations like this, where you actually need to access that foreign key column, you're screwed. That way around that is to explicitly define a property to hold that relationship on your model and tell Entity Framework to use that instead of creating its own.
[ForeignKey("Client")]
public int ClientId { get; set; }
public virtual Client Client { get; set; }
With that, you can now directly use ClientId without worrying about filling in Client. You again bind your drop down list to ClientId, but now, you do not need to look up the client explicitly from the database. Entity Framework will just save the ClientId as it should to the database, and then restore the Client based on that when you look up the project again in the future.

Cannot update many-to-many relationships in Entity Framework

I am using Entity Framework 4.3 Code First, and I have problem with updating many-to-many relationships.
I defined the following classes:
public abstract class Entity
{
[Column(Order = 0)]
[Key]
public int Id { get; set; }
[Timestamp]
[Column(Order = 1)]
public byte[] Version { get; set; }
}
public class Video : Entity
{
public string Title { get; set; }
public string Description { get; set; }
public TimeSpan Length { get; set; }
public virtual ICollection<Coworker> Coworkers { get; set; }
}
public class Coworker : Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Video> Videos { get; set; }
}
When the database is created, the schema look right:
There is a Videos, Coworkers and VideoCoworkers table too, without
I use repository pattern in an N-Tier application to access database, my Insert and Update method looks like this:
public T Insert(T entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
session.Context.Set<T>().Add(entity);
}
}
public T Update(T entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
entity = session.Context.Set<T>().Attach(entity);
session.Context.Entry(entity).State = EntityState.Modified;
}
return entity;
}
When I update an entity, I create the entity object from a DTO, that's why use DbSet.Attach instead of selecting it and updating the properties one-by-one.
When I initialize the database, I add some test data:
Create 3 Coworkers, where I set first and last name. (A, B, C)
Create 3 Videos, where I set title, description and length, and also set some coworkers. First video has A,B, second has B,C and third has A,C.
When I list the Videos from code, I can see that Video.Coworkers collection is filled with good values, and when I query the link table (VideoCoworkers) in SQL Server Management Studio, it also looks good.
My problem is
when I update for example the title of the Video, it works. But when I try to delete from Video2 the existing coworkers (B and C), and try to add coworker A, then the relationship is not updated. It also does not work when I only try to add new coworker, or only try to delete one. I create the entity which is used as the parameter of the Update() method by creating a new Video entity with a new collection of Coworkers (which are selected from the database with Find() method by Id).
What is the correct way to update many-to-many relationships?
But when I try to delete from Video2 the existing coworkers (B and C),
and try to add coworker A, then the relationship is not updated.
Without using a generic repository the correct procedure would be:
using (var session = new DatabaseSession())
{
video2 = session.Context.Set<Video>().Include(v => v.Coworkers)
.Single(v => v.Id == video2Id);
coworkerA = new Coworker { Id = coworkerAId };
session.Context.Set<Coworker>().Attach(coworkerA);
video2.Coworkers.Clear();
video2.Coworkers.Add(coworkerA)
session.Context.SaveChanges();
}
The essential part is that you must load or attach the entity in its original state, change the entity, i.e. remove and add children, and then save the changes. EF's change detection will create the necessary INSERT and DELETE statements for the link table entries. The simple procedure to set the state to Modified you are trying in your generic Update method is suited only for updating scalar properties - like changing the video title - but won't work for updating relationships between entities.
For solve this problem:
attach the entity to context
load the collection(the collection is not loaded, because )
change the state of entity to modified
save changes
So your code for update should be like this:
public Video Update(Video entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
entity = session.Context.Set<Video>().Attach(entity);
session.Context.Entry(entity).Collection(p => p.Coworkers).Load();
session.Context.Entry(entity).State = EntityState.Modified;
}
return entity;
}
Please refer here to see how to save master detail in asp.net mvc with database first. Hopefully it will give you the idea about the code first. You may also have a look at knokout.js example

Cannot save new object in a one to one relationship

I am using this article to make a one-to-one relationship between my two objects - Site and WebOptions. Site is already present. a record in WebOptions may or may not be present. When it is, my mappings work fine. When it is not, my system blows up trying to create a new record.
Here is my site class (the important bits)
public class Site : CoreObjectBase
{
public virtual int SiteId { get; set; }
public virtual WebOptions WebOptions { get; set; }
}
And here is my web options class (important parts again)
public class WebOptions : CoreObjectBase
{
public virtual int WebOptionsId { get; set; }
private int SiteId { get; set; }
private Site Site { get; set; }
}
And the mapping for Site is
HasOne<WebOptions>(x => x.WebOptions)
.Cascade.All();
And the mapping for WebOptions is
Id(Reveal.Property<WebOptions>("SiteId")).GeneratedBy.Foreign("Site");
HasOne<Site>(Reveal.Member<WebOptions, Site>("Site"))
.Constrained()
.ForeignKey();
In the data, the table behind Site has no foreighn key field to WebOptions, but the table behind WebOptions contains the SiteId. In my code, I am already getting the site and use site.WebOptions.SomeSetting and would like to keep it that way.
My problem is this. If I deviate from this mapping at all, my model breaks and no weboptions are returned while several records are saved into the weboptions table (duplicates). But, when I try to save a new WebOptions object, I get
Batch update returned unexpected row count from update; actual row
count: 0; expected: 1
I have a repository class with 2 save methods:
public sealed class Repository<T> : IRepository<T> where T : CoreObjectBase
{
public void SaveWithDependence<K>(T entity, K dependant) where K : CoreObjectBase
{
entity.Validate();
dependant.Validate();
using (ITransaction tx = Session.BeginTransaction())
{
Session.SaveOrUpdate(entity);
Session.SaveOrUpdate(dependant);
tx.Commit();
}
}
public void Save(T entity)
{
entity.Validate();
using (ITransaction tx = Session.BeginTransaction())
{
Session.SaveOrUpdate(entity);
tx.Commit();
}
}
}
When no WebOptions is found, I am doing this when making a new one:
var options = site.WebOptions;
if (options == null)
{
options = new WebOptions(site);
site.WebOptions = options;
}
And the constructor looks like this to set the private variables
public WebOptions(Site site)
{
Site = site;
SiteId = site.SiteId;
}
And then to save, I have tried to following:
siteRepository.Save(site);
and
siteRepository.SaveWithDependence(site, options);
and
optionsRepository.Save(options);
and
optionsRepository.SaveWithDependence<Site>(options, site);
All of them return the above error. My session declaration looks like this
sessionFactory =
Fluently.Configure().Database(
FluentNHibernate.Cfg.Db.MsSqlConfiguration.MsSql2005.DefaultSchema("dbo")
.ConnectionString(c => c
.FromConnectionStringWithKey("MyDatabase"))
.AdoNetBatchSize(20))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<SessionManager>())
.ExposeConfiguration(x => x.SetProperty("current_session_context_class", "managed_web"))
.BuildSessionFactory();
I really need to be able to save a new WebOptions record if one doesn't exist, but I can't seem to get it to work with my one-to-one relationship.
Wow, I spent all that time putting that together and then in playing around with it, I removed one line of code - just to see what would happen.
In the constructor for WebOptions I removed this single line:
SiteId = site.SiteId;
for a constructor that looks like this:
public WebOptions(Site site)
{
Site = site;
}
Then, I save only my WebOptions object like this:
optionsRepository.Save(options);
My best guess is that since I am using an ID field for the 'SiteId' property in fluent, fluent doesn't allow me to manually set that value. Setting the private property Site in addition to setting the Site.WebOptions property, must set up the one-to-one relationship for fluent/nhibernate to deduce what value to place into the SiteId field.
Further inspection of the article posted above shows that this is the way it has to be done. I just happened to miss this very important piece of information:
The public constructor, taking a Client parameter, is the one you will use in your code whenever you want to assign a client some alimentary habits, such as: AlimentaryHabits = new AlimentaryHabits(this);. The protected constructor is used internally by NHibernate, and must be present. You can completely ignore it.
I am going to leave this post and answer in the event someone else has this issue and I can save them a little bit of time and frustration.

Categories