How do I map a collection accessed through a read

2019-08-30 12:53发布

问题:

How do I map the collection of Parts using a convention?

public class Part
{
  public virtual int Id { get; set; }
  public virtual string Name { get; set; }
}

public class Car
{
  private readonly List<Part> _parts = new List<Part>();

  public virtual int Id { get; set; }

  public virtual IList<Part> Parts
  {
    get { return _parts.AsReadOnly(); }
  }
}

I have tried this convention but it always expects the field name without an underscore prefix:

public class HasManyConvention : IHasManyConvention
{
  public void Apply(IOneToManyCollectionInstance instance)
  {
    instance.Access.ReadOnlyPropertyThroughCamelCaseField(CamelCasePrefix.Underscore);
  }
}

I've tried it with the 1.2.0.694 and 2.0.0.698 builds with the same result:

"Could not find field 'parts' in class 'TestFluentNHibernate.Car'"

回答1:

First of all, your _parts member can't be read-only. NHibernate needs write access to the member to set the value/reference. To return a truly read-only collection through the Parts property you have to return a System.Collections.ObjectModel.ReadOnlyCollection. This also "removes" all the methods found in the read/write collection types that are still there if you just return f.ex. list.AsReadOnly(). A read-only list returned in this way still have .Add() method and others to edit the collection, but they will cause a runtime exception so returning a ReadOnlyCollection to begin with is a great for preventing this possibility.

A lot of people seem to like returning an IEnumerable which is also read-only, but it can be cast to a List or other read/write collection type and changed that way.

You have to implement AddPart and RemovePart methods to allow external code to add and remove items to the read-only collection.

Your convention looks correct, I'm using the exact same syntax with 1.2.0.694 successfully, my convention:

     instance.Key.Column(instance.EntityType.Name + "Fk");
     instance.Fetch.Subselect();
     instance.Inverse();
     instance.Cascade.All();
     instance.Access.ReadOnlyPropertyThroughCamelCaseField(CamelCasePrefix.Underscore);

Example class:

public class Car
{
    private List<Part> _parts;

    public Car()
    {
        // Initialize member collection _parts
        _parts = new List<Part>();
    }

    public virtual int Id { get; set; }

    // Return readonlycollection here, ReadOnlyCollection implements 
    // IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable,
    // so the returned list can be any collection type inheriting from these base types.
    public ReadOnlyCollection<Part> Parts
    {
        get { return new List<Part>(_parts).AsReadOnly(); } }
    }

    public AddPart(Part part)
    {
        // Usually I don't want duplicates
        if (!_parts.Contains(part))
            _parts.Add(part);
    }
}


回答2:

Hopefully this helps. Try...

public class Car
{
  protected virtual List<Part> _parts { get;set; }

  public Car()
  {
    // Initialize "Parts" to not be null
    this.Parts = new List<Part>();
  }
  public void PopulateParts (int someID)
  {
    // Do a call out to one of your helpers to populate "Parts"
    // so it can be used.
    //this.Parts = SomeCall(someID);
  }

  public virtual int Id { get; set; }

  public virtual IList<Part> Parts
  {
    get { return this._parts; } // No need for "ReadOnly" since "protected set" will prevent changes outside of this class.
    protected set { this._parts = value; }
  }
}

Protected set is going to make this a read only element outside of the Car class. Since I don't see how you are going to populate Parts, I created a call "PopulateParts" as a hook so you can populate the List.