Domain mapping issue in plugin development (navigation properties)

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.
12 years ago
Update: In the post about making a plugin with data access at:
http://blog.csharpwebdeveloper.com/2011/09/23/nopcommerce-plugin-with-data-access/#comment-59
Skyler Severns said "One other thing to note is that we do not have navigation properties (relational properties), and I’ll cover those in more detail later."

I think this is what I am trying to do - make use of the navigation property 'Address'. Does anyone know how this is done? Thanks.

------------------------------

I'm trying to develop my first plugin which is to be a dealer locator, under 2.2. Seeing how the address table that already exists contains the exact fields that are needed for my dealers, I was trying to reuse it. I figured I could do this since it's already in use by a few different sections (customers/affiliates).

The issue I'm having is this. If I include an address object reference in my dealer baseentity class, when I try to install the plugin, it then tries to recreate address related tables, which causes it to fail. If I don't include the address reference in the baseentity, then the foreign key reference I'm trying to set in the mapping file errors out (the same foreignkey the affiliate table uses).

Listed below are the baseentity and mapping classes I'm using. I can list any of the other plugin files if you think it would help, but I don't think they have any issues in them at this point--they are mostly made up from the examples on blog.csharpwebdeveloper.com or existing nop core plugins that access the database.

If any one that can provide any help to point me in the right directly it would be greatly appreciated.

DealerRecord.cs

using Nop.Core;
using Nop.Core.Domain.Common;

namespace Nop.Plugin.Misc.Dealers.Domain
{
    /// <summary>
    /// Represents a dealer record
    /// </summary>
    public partial class DealerRecord : BaseEntity
    {
        public virtual int AddressId { get; set; }
        public virtual bool Active { get; set; }
        public virtual string DealerWebSite { get; set; }

        public virtual Address Address { get; set; }
    }
}


DealerRecordMap.cs

using System.Data.Entity.ModelConfiguration;
using Nop.Plugin.Misc.Dealers.Domain;

namespace Nop.Plugin.Misc.Dealers.Data
{
    public partial class DealerRecordMap : EntityTypeConfiguration<DealerRecord>
    {
        public DealerRecordMap()
        {
            this.ToTable("Dealer");
            this.HasKey(d => d.Id);
            this.Property(d => d.AddressId);
            this.Property(d => d.Active);
            this.Property(d => d.DealerWebSite).HasMaxLength(255).IsOptional();

            this.HasRequired(d => d.Address)
                .WithMany()
                .HasForeignKey(x => x.AddressId)
                .WillCascadeOnDelete(false);
        }
    }
}


I hope I posted this is the right section since I'm fairly new around here. :)
12 years ago
Geralt wrote:
Update: In the post about making a plugin with data access at:
http://blog.csharpwebdeveloper.com/2011/09/23/nopcommerce-plugin-with-data-access/#comment-59
Skyler Severns said "One other thing to note is that we do not have navigation properties (relational properties), and I’ll cover those in more detail later."

I think this is what I am trying to do - make use of the navigation property 'Address'. Does anyone know how this is done? Thanks.

------------------------------

I'm trying to develop my first plugin which is to be a dealer locator, under 2.2. Seeing how the address table that already exists contains the exact fields that are needed for my dealers, I was trying to reuse it. I figured I could do this since it's already in use by a few different sections (customers/affiliates).

The issue I'm having is this. If I include an address object reference in my dealer baseentity class, when I try to install the plugin, it then tries to recreate address related tables, which causes it to fail. If I don't include the address reference in the baseentity, then the foreign key reference I'm trying to set in the mapping file errors out (the same foreignkey the affiliate table uses).

Listed below are the baseentity and mapping classes I'm using. I can list any of the other plugin files if you think it would help, but I don't think they have any issues in them at this point--they are mostly made up from the examples on blog.csharpwebdeveloper.com or existing nop core plugins that access the database.

If any one that can provide any help to point me in the right directly it would be greatly appreciated.

DealerRecord.cs

using Nop.Core;
using Nop.Core.Domain.Common;

namespace Nop.Plugin.Misc.Dealers.Domain
{
    /// <summary>
    /// Represents a dealer record
    /// </summary>
    public partial class DealerRecord : BaseEntity
    {
        public virtual int AddressId { get; set; }
        public virtual bool Active { get; set; }
        public virtual string DealerWebSite { get; set; }

        public virtual Address Address { get; set; }
    }
}


DealerRecordMap.cs

using System.Data.Entity.ModelConfiguration;
using Nop.Plugin.Misc.Dealers.Domain;

namespace Nop.Plugin.Misc.Dealers.Data
{
    public partial class DealerRecordMap : EntityTypeConfiguration<DealerRecord>
    {
        public DealerRecordMap()
        {
            this.ToTable("Dealer");
            this.HasKey(d => d.Id);
            this.Property(d => d.AddressId);
            this.Property(d => d.Active);
            this.Property(d => d.DealerWebSite).HasMaxLength(255).IsOptional();

            this.HasRequired(d => d.Address)
                .WithMany()
                .HasForeignKey(x => x.AddressId)
                .WillCascadeOnDelete(false);
        }
    }
}


I hope I posted this is the right section since I'm fairly new around here. :)


Welcome to the community Geralt. Andrei or an EF4 pro would be the best resource on answering this question, but here is my attempt. Keep in mind my solution has not been tested and is an off the hip solution.

The context
Make your object context include the entity configurations for all required classes. You could use reflection to determine all the mapping files similar to how the NopObjectContext works. Once you've mapped all the classes you should be able to work with previously existing navigation properties. Note: make sure your new context is configured to have a per-request life-cycle in the dependency registrar.  

The installation service
You should create a new service class for installing your plugin. This class will be responsible for installing locale resources, the schema, and default data. Do not use entity framework to generate your schema on installation. Create a SQL script that defines your portion of the schema and make the script an embedded resource in your plugin. During installation read your SQL script and execute it against the database. You should make your installation service idempotent.

I hope this helps move you forward.
12 years ago
Actually if you want to have a new entity in a plugin module, then you need to create a new ObjectContext. For example, DealerObjectContext. Look at 'Shipping.ByWeight' or 'Nop.Plugin.Tax.CountryStateZip' plugins in order to know how it's done. But the issue here is Entity Framework. It does not allow belonging of the same entity to two distinct contexts ('Address' in your case). So you just can't have 'Address' navigation property in 'Dealer' in this case.

Solution 1. Just use 'AddressId' property to load appropriate address using 'IAddressService'.
DealerRecord.cs

using Nop.Core;
using Nop.Core.Domain.Common;

namespace Nop.Plugin.Misc.Dealers.Domain
{
    /// <summary>
    /// Represents a dealer record
    /// </summary>
    public partial class DealerRecord : BaseEntity
    {
        public virtual int AddressId { get; set; }
        public virtual bool Active { get; set; }
        public virtual string DealerWebSite { get; set; }
    }
}


DealerRecordMap.cs

using System.Data.Entity.ModelConfiguration;
using Nop.Plugin.Misc.Dealers.Domain;

namespace Nop.Plugin.Misc.Dealers.Data
{
    public partial class DealerRecordMap : EntityTypeConfiguration<DealerRecord>
    {
        public DealerRecordMap()
        {
            this.ToTable("Dealer");
            this.HasKey(d => d.Id);
            this.Property(d => d.AddressId);
            this.Property(d => d.Active);
            this.Property(d => d.DealerWebSite).HasMaxLength(255).IsOptional();
        }
    }
}



Solution 2. Another solution will be adding 'DealerRecordMap' to default NopObjectContext (Nop.Data assembly). But it's no so "pluggable" now
12 years ago
First, thank you both for your replies. I will reply seperately to them so it's easier if you care to follow up.

Re: skyler.severns

First I gotta say thank you for going through the trouble of making your examples on csharpwebdeveloper for people like me just starting out. They have been extremely helpful.

My BaseEntity record had the address object added because I looked at both the affiliate domain class and the customer domain class and they both had it in there, and they are both making their own records in address database table, what I wanted to do.

Affiliate.cs - included "public virtual Address Address { get; set; }"
Customer.cs - included " public virtual Address BillingAddress { get; set; }"
                                    public virtual Address ShippingAddress { get; set; }

I gotta say at my current level of understanding, the details in your "The context" are alittle over my head. I definately want to tie into the address "services" at some point like the affiliates does, as when I add/edit/delete a dealer I want to be able to add/edit/delete the accompaning address record as well. I haven't started on this yet though, so maybe when I do this will make more sense

But what your suggesting in the "The installation service" makes sense to me more. I have the install classes already set (which are just basic copies of the classes from the plugins that use the db like "Nop.Plugin.Feed.Froogle"). So rather than use the CreateDatabaseScript(); which I believe is from the EF, use my own sql here. Makes sense.

Right now I was able to move forward by just making my new dealer table manually and commenting out the address references noted in the files I posted, then after the plugin was installed, I just uncommented the references. But obviously that isn't the way I want to leave it.

Worse case here I just replicate the address fields in my own dealer table and don't even bother trying to use the existing address stuff if this proves too difficult on my first plugin attempt (with deadlines to meet). I was just really hoping to take advantage of what nop already has built in.
12 years ago
Re. a.m.

Thanks so much for your reply. I do have a "DealerObjectContext.cs" class already, which is just basically a copy of the class from an existing plugin that uses the db (like the ones you mentioned). I also looked through every built in nop plugin and all the ones you can get from the site here, but couldn't find any plugin that uses navigation properties in it's baseentity.

"But the issue here is Entity Framework. It does not allow belonging of the same entity to two distinct contexts ('Address' in your case). So you just can't have 'Address' navigation property in 'Dealer' in this case. "

I saw you post this in a previous thread, but I will admit I don't undestand the full extent of it.

Looking through the base nop files, both the affiliates and the customers BaseEntitys both have a reference to the address navigation property...
public virtual Address (Billing/Shipping)Address { get; set; }

I was just trying to do what they are doing in my own plugin. Do core baseentities work different then plugin ones, or can i make a plugin one work as they do? How are they both able to reference the same Address object in their baseentities?


With your idea noted in "Solution 1", again sorry I don't fully grasp it at this point, but it sounds promising. I can certainlly start looking into that - would tieing into this service allow me to add/edit/delete address records alongside of my own new dealer records?

I wouldn't go the route of trying your "Solution 2". If that was the case I would probably just change the core files as I needed/add on to them to make my dealer locator and wouldn't even bother trying to make it a plugin. But I wanted to learn how to do this properly from the start as opposed to taking shortcuts.
12 years ago
Had a quick update, and I thought I'd post it just in case anyone else ever runs into this same problem, which caused me a huge headache.

I left the custom installation script for now, manually made the tables, and moved in to try to tap into the existing addresses classes for my own plugin use.

Long story short, as far as I could tell I setup everything required to get this working (with direction from the above posts), but for the life of me it just wouldn't run. I kept getting a "Message=Invalid object name 'dbo.Addresses' error when I tried to populate an AddressModel object on the frontend. I looked everywhere for an "addresses" reference in nop but came up empty - i had no idea why it was looking for this table and not just "address".

In the end I stumbled upon something called Pluralizing for the entity framework. From here, I ended up adding a:

modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

line to my OnModelCreating method in my DealerObjectContext file, everything else started working right away. I was able to reference my dealer record and it brought back the matching address record with it for DealerRecord.address, which is awesome as it's what I've been trying to do. (Hopefully getting the admin section up for adding/editing/deleting my dealer records won't be this hard. :) )

So has anybody heard of this behavior before? I can see no other uses of PluralizingTableNameConvention used within the NOP framework. If this auto renaming isn't an issue anywhere else in nop, any ideas as to why my plugin was trying to locate an "addresses" table instead of "address" by default. Is the issue maybe in my local database setup or within the plugin setup itself?
This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.