简体   繁体   中英

One to Many Mapping in Fluent Nhibernate

I am working on Fluent Nhibernate and I am getting the following error:

"Cannot insert the value NULL into column 'EmailAccountId', table 'NopCommerceNew123.dbo.QueuedEmail'; column does not allow nulls. INSERT fails.\\r\\nThe statement has been terminated."} could not insert: [Nop.Core.Domain.Messages.QueuedEmail][SQL: INSERT INTO QueuedEmail ([Priority], [From], FromName, [To], ToName, CC, Bcc, [Subject], Body, AttachmentFilePath, AttachmentFileName, CreatedOnUtc, SentTries, SentOnUtc, EmailAccountId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); select SCOPE_IDENTITY()]

Please let me know about the error in this.Thanks.

Here Are My Classes and mappings:

QueueEmail.cs

public  partial class QueuedEmail : BaseEntity
{
    /// <summary>
    /// Gets or sets the priority
    /// </summary>
    public virtual  int Priority { get; set; }

    /// <summary>
    /// Gets or sets the From property
    /// </summary>
    public virtual  string From { get; set; }

    /// <summary>
    /// Gets or sets the FromName property
    /// </summary>
    public virtual  string FromName { get; set; }

    /// <summary>
    /// Gets or sets the To property
    /// </summary>
    public virtual  string To { get; set; }

    /// <summary>
    /// Gets or sets the ToName property
    /// </summary>
    public virtual  string ToName { get; set; }

    /// <summary>
    /// Gets or sets the CC
    /// </summary>
    public virtual  string CC { get; set; }

    /// <summary>
    /// Gets or sets the Bcc
    /// </summary>
    public virtual  string Bcc { get; set; }

    /// <summary>
    /// Gets or sets the subject
    /// </summary>
    public virtual  string Subject { get; set; }

    /// <summary>
    /// Gets or sets the body
    /// </summary>
    public virtual  string Body { get; set; }

    /// <summary>
    /// Gets or sets the attachment file path (full file path)
    /// </summary>
    public virtual  string AttachmentFilePath { get; set; }

    /// <summary>
    /// Gets or sets the attachment file name. If specified, then this file name will be sent to a recipient. Otherwise, "AttachmentFilePath" name will be used.
    /// </summary>
    public virtual  string AttachmentFileName { get; set; }

    /// <summary>
    /// Gets or sets the date and time of item creation in UTC
    /// </summary>
    public virtual  DateTime CreatedOnUtc { get; set; }

    /// <summary>
    /// Gets or sets the send tries
    /// </summary>
    public virtual  int SentTries { get; set; }

    /// <summary>
    /// Gets or sets the sent date and time
    /// </summary>
    public virtual  DateTime? SentOnUtc { get; set; }

    /// <summary>
    /// Gets or sets the used email account identifier
    /// </summary>
    public virtual  int EmailAccountId { get; set; }

    /// <summary>
    /// Gets the email account
    /// </summary>
    public virtual  EmailAccount EmailAccount { get; set; }
}

EmailAccount.cs

public  partial class EmailAccount : BaseEntity
{
    /// <summary>
    /// Gets or sets an email address
    /// </summary>
    public virtual  string Email { get; set; }

    /// <summary>
    /// Gets or sets an email display name
    /// </summary>
    public virtual  string DisplayName { get; set; }

    /// <summary>
    /// Gets or sets an email host
    /// </summary>
    public virtual  string Host { get; set; }

    /// <summary>
    /// Gets or sets an email port
    /// </summary>
    public virtual  int Port { get; set; }

    /// <summary>
    /// Gets or sets an email user name
    /// </summary>
    public virtual  string Username { get; set; }

    /// <summary>
    /// Gets or sets an email password
    /// </summary>
    public virtual  string Password { get; set; }

    /// <summary>
    /// Gets or sets a value that controls whether the SmtpClient uses Secure Sockets Layer (SSL) to encrypt the connection
    /// </summary>
    public virtual  bool EnableSsl { get; set; }

    /// <summary>
    /// Gets or sets a value that controls whether the default system credentials of the application are sent with requests.
    /// </summary>
    public virtual  bool UseDefaultCredentials { get; set; }


    public virtual ICollection<QueuedEmail> QueueEmail { get; set; }

    /// <summary>
    /// Gets a friendly email account name
    /// </summary>
    public virtual  string FriendlyName
    {
        get
        {
            if (!String.IsNullOrWhiteSpace(this.DisplayName))
                return this.Email + " (" + this.DisplayName + ")";
            return this.Email;
        }
    }

QueuedEmailMap.cs:

 public class QueuedEmailMap : ClassMap<QueuedEmail>
 {
        public QueuedEmailMap()
        {
            Table("QueuedEmail");
            LazyLoad();
            Id(x => x.Id).GeneratedBy.Identity().Column("Id");          
            Map(x => x.Priority).Column("[Priority]").Not.Nullable().Precision(10);
            Map(x => x.From).Column("[From]").Not.Nullable().Length(500);
            Map(x => x.FromName).Column("FromName").Length(500);
            Map(x => x.To).Column("[To]").Not.Nullable().Length(500);
            Map(x => x.ToName).Column("ToName").Length(500);
            Map(x => x.CC).Column("CC").Length(500);
            Map(x => x.Bcc).Column("Bcc").Length(500);
            Map(x => x.Subject).Column("[Subject]").Length(1000);
            Map(x => x.Body).Column("Body");
            Map(x => x.AttachmentFilePath).Column("AttachmentFilePath");
            Map(x => x.AttachmentFileName).Column("AttachmentFileName");
            Map(x => x.CreatedOnUtc).Column("CreatedOnUtc").Not.Nullable();
            Map(x => x.SentTries).Column("SentTries").Not.Nullable().Precision(10);
            Map(x => x.SentOnUtc).Column("SentOnUtc");
            //References(x => x.EmailAccount).Class<EmailAccount>().Columns("EmailAccountId");
              References(x => x.EmailAccount).Column("EmailAccountId").Not.Nullable().Cascade.All();
        }
    }

EmailAccountMap.cs:

public class EmailAccountMap : ClassMap<EmailAccount>
{
    public EmailAccountMap()
    {   Table("EmailAccount");
        LazyLoad();
        Id(x => x.Id).GeneratedBy.Identity().Column("Id");
        Map(x => x.Email).Column("Email").Not.Nullable().Length(255);
        Map(x => x.DisplayName).Column("DisplayName").Length(255);
        Map(x => x.Host).Column("Host").Not.Nullable().Length(255);
        Map(x => x.Port).Column("Port").Not.Nullable().Precision(10);
        Map(x => x.Username).Column("Username").Not.Nullable().Length(255);
        Map(x => x.Password).Column("Password").Not.Nullable().Length(255);
        Map(x => x.EnableSsl).Column("EnableSsl").Not.Nullable();
        Map(x => x.UseDefaultCredentials).Column("UseDefaultCredentials").Not.Nullable();

    }
}

The solution here would be to assign EmailAccount of the QueuedEmail instance, when adding it into the collection of QueuedEmails . This should be the code:

// method somewhere in the 'EmailAccount' definiton
public void AddEmail(QueuedEmail email)
{
    this.QueueEmail.Add(email)
    email.EmailAccount = this;
}

this will correctly assign the value "EmailAccount" into the column "EmailAccountId" during the INSERT.

The reason is: we declared the collection of QueueEmail as inverse

HasMany<QueuedEmail>(x => x.QueueEmail)
  .KeyColumn("EmailAccountId")
  .Inverse() // here we say inverse
  .Cascade.All(); 

And inverse is a sign for NHibernate: the child does know about its parent reference - child will care about self (because it has enough information) . And with the adjustment above (assigning the parent) all will work.

EDIT: how to fix the next issue:

"not-null property references a null or transient value QueuedEmail.EmailAccount."

The problem is, that when we call: session.Save(queuedEmail) the queuedEmail instance must have set the reference EmailAccount . It is not enough to set integer EmailAccountId ! Why? because it is not mapped. And there is in fact solution

So, if we always can be sure, that we have the EmailAccountId , we can use this mapping:

public class QueuedEmailMap : ClassMap<QueuedEmail>
{
    public QueuedEmailMap()
    {
        ...
        // this property will be WRITABLE
        Map(x => x.x.EmailAccountId)
          .Column("EmailAccountId")

        // this one will be readonly
        References(x => x.EmailAccount)
          .Column("EmailAccountId")
          .Not.Nullable()
          .Cascade.All()
          .Not.Insert() // this is the setting
          .Not.Update()
          ;
    ...

From this moment, we can set just the reference Id and all will work.

NOTE: I do also use the doubled mapping (Id and Reference), but the readonly is the int Id

You can add the property twice like Radim says. Or delete the property EmailAccountId from QueuedEmail class and just add this to your mapping:

public class EmailAccountMap : ClassMap<EmailAccount>
{
    public EmailAccountMap()
    {
        // your code

        HasMany<QueuedEmail>(x => x.QueueEmail)
           .KeyColumn("EmailAccountId")
           .Inverse()
           .Cascade.All(); 
    }
}
public class QueuedEmailMap : ClassMap<QueuedEmail>
{
    public QueuedEmailMap()
    {
        // your code

        References(x => x.EmailAccount)
          .Column("EmailAccountId")
          .Not.Nullable();
    }
}

Don't forget call:

session.Save(emailAccount)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM