Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
182 views
in Technique[技术] by (71.8m points)

breeze - Working with beforeSaveEntity and Navigation Properties

My application allows the user to create products along with their UOM (Units of Measurement) and Barcodes During the creation process, API will check if there is no barcode entered, it will generate it automatically. That worked fine until I decided to add weight products that require scale barcodes with 7 digits. BeforeSaveEntity will ask if the product type is weight then generate 7 digits barcode, else, it will generate 13 digits.

The problem is; I can't get this to work when checking the parent table, here is my code:

Models: (for convenience, I have omitted unneeded properties.)

 public class Product
    {   public int Id { get; set; }
        public ProductName { get; set; }
        public int ClassId { get; set; }
        public  ICollection<Unit> Units { get; set; }
    }

    public class Unit
   {
    public int Id { get; set; }
    [ForeignKey("Product")]
    public int ProdId { get; set; }
    public int PackId { get; set; }
    public decimal PackUnits { get; set; }
    public Product Product { get; set; }
    public  ICollection<Barcode> Barcodes { get; set; }
    }
    public class Barcode
    {
    public int Id { get; set; }
    [ForeignKey("Unit")]
    public int UnitId { get; set; }
    public string Bcode { get; set; }
    [DefaultValue("false")]
    public bool IsSystemGenerated { get; set; }
    public Unit Unit { get; set; }
    }

Before Save Entity:

     protected override bool BeforeSaveEntity(EntityInfo entityInfo)
    {  
        if (entityInfo.Entity.GetType() == typeof(Barcode)
          && (entityInfo.EntityState == Breeze.ContextProvider.EntityState.Added || entityInfo.EntityState == Breeze.ContextProvider.EntityState.Modified))
        {
            var barcode = (Barcode)entityInfo.Entity;
            var product = (Product)barcode.Unit.Product; // The problem is here

            int classId = product.ClassId;// Hence, can't get this guy

            string bcode = barcode.Bcode;


            if (String.IsNullOrEmpty(bcode))
            {
                if (classId != 2) // Check if the product type is not weight
                    barcode.Bcode = GenerateBarcode();
                else // Otherwise generate scale barcode
                    barcode.Bcode = GenerateScaleBarcode(); 

                barcode.IsSystemGenerated = true;   
            }

            return true;
        }
        else
            return true;
    }

     protected override Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap)
    {
        return saveMap;
    }

Acquiring the parent navigation property Product gives an error message:

Object reference not set to an instance of an object

Disabling the classId checking, Insert takes place in the following order: Product => Unit => Barcode which in this case, Barcode entity should give information on it's parents Unit then Product.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I understand. I want to make a strong claim that you should not do what you are trying do using lazy navigation.

I strongly discourage almost all developer use of the Breeze EF context inside a BeforeSave... method. I'm referring specifically to the EF context that holds your changed barcode entity.

The Breeze context is reserved for Breeze's own use during the save process. You shouldn't put anything into it that isn't destined for save. That includes entities retrieved by lazy navigation. IMO it is fortunate that you can't navigate.

Why? Security is the most important reason. I can't really trust data from the client. When validating or manipulating client changes, I should get my truth from the database itself.

For the same reason I always ignore the values in the originalValues object that came from the client. Never use these values for validation. They are useful primarily for concurrency checking and to help EF figure out the proper save order. The originalValues property names are important - they tell EF which columns to update - but their values, other than FK and optimistic concurrency property values, are immaterial. Again, don't trust them.

Obviously you have to trust some of the client input ... otherwise you couldn't save anything. But it is wise to limit the scope of that trust to the "safe values" that your validation logic permits this user to create or change.

I fear we have not made these points strongly enough in our documentation.

What should you do?

I am of the firm opinion that you should spin up a new, separate EF context for use in validation and inquiry. That context can be used by other BeforeSave... activities during the lifetime of the save request.

I populate this read-only EF context directly from the database. I keep it and all of the entities queried into it completely isolated from the Breeze save-context and its change-set entities.

In your case, having created that read-only context, I'd extract the client-supplied barcode key information and do an expand query to get the related Unit and Product information. Then I'd use that information to update the client-supplied barcode as you've described.

Disagree with me? Nothing stops you from loading the related properties explicitly with the Breeze EF context. You just can't lazy load.

It follows from this that Breeze should not tempt you with lazy loading navigation of the entities in EntityInfos and should not tempt you into using the Breeze EF context for any purpose other than the preparation of the final collection of entities to be saved.

p.s. Serialization is another reason that lazy load is disallowed. When EF saves changes successfully, Breeze prepares a save result with the saved entities and returns this result to the client. When Json.Net serializes the save result, it wanders down all navigation paths and serializes whatever it finds. If lazy loading were enabled, it would (slowly) pull in tons of related entities from the database and send them too. That is highly undesirable.

p.p.s. Of course we could turn off "lazy load" just before releasing the save result for serialization. But if you had eagerly or lazy loaded the related Unit and Product entities into the Breeze EF Context, these too would be serialized and sent to the client in the save result. Not good.

Don't put anything in the Breeze context that isn't supposed to be saved.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...