简体   繁体   中英

One to many relationship doesn`t retrieve data in entity framework

I`m in process of learning C# & .NET and EF (with aspnetboilerplate ) and I came up with idea to create some dummy project so I can practice. But last 4 hour Im stuck with this error and hope someone here can help me.

What I create( well at least I think I create it correctly ) is 2 class called "Ingredient" and "Master"

I want to use it for categorize Ingredient with "Master" class.

For example ingredient like

  • Chicken breast
  • chicken drumstick

Both of them belong to Meat ( witch is input in "Master" database ) and here is my code

Ingredient.cs

   public class Ingrident : Entity
{

    public string Name { get; set; }
    public Master Master { get; set; }
    public int MasterId { get; set; }

}

Master.cs

public class Master : Entity

    {
        public string Name { get; set; }
        public List<Ingrident> Ingridents { get; set; } = new();
    
    }

IngridientAppService.cs

public List<IngridientDto> GetIngWithParent()
    {
        var result = _ingRepository.GetAllIncluding(x => x.Master);
         //Also I try this but doesn`t work 
        // var result = _ingRepository.GetAll().Where(x => x.MasterId == x.Master.Id);
        return ObjectMapper.Map<List<IngridientDto>>(result);
    }

IngridientDto.cs

  [AutoMap(typeof(IndexIngrident.Entities.Ingrident))]

public class IngridientDto : EntityDto
{
    public string Name { get; set; }
    public List<MasterDto> Master { get; set; }

    public int MasterId { get; set; }
 }

MasterDto.cs

    [AutoMap(typeof(IndexIngrident.Entities.Master))]

    public class MasterDto : EntityDto
    {
     public string Name { get; set; }
    }

When I created ( for last practice ) M -> M relationship this approach with .getAllIncluding work but now when I have One -> Many it won`t work.

Hope someone will be able to help me or at least give me some good hint.

Have a nice day !

Straight up the examples you are probably referring to (regarding the repository etc.) are overcomplicated and for most cases, not what you'd want to implement.

The first issue I see is that while your entities are set up for a 1-to-many relationship from Master to Ingredients, your DTOs are set up from Ingredient to Masters which definitely won't map properly.

Start with the simplest thing. Get rid of the Repository and get rid of the DTOs. I'm not sure what the base class "Entity" does, but I'm guessing it exposes a common key property called "Id". For starters I'd probably ditch that as well. When it comes to primary keys there are typically two naming approaches, every table uses a PK called "Id", or each table uses a PK with the TableName suffixed with "Id". Ie "Id" vs. "IngredientId". Personally I find the second option makes it very clear when pairing FKs and PKs given they'd have the same name.

When it comes to representing relationships through navigation properties one important detail is ensuring navigation properties are linked to their respective FK properties if present, or better, use shadow properties for the FKs.

For example with your Ingredient table, getting rid of the Entity base class:

[Table("Ingredients")]
public class Ingredient : Entity
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int IngredientId { get; set; }
    public string Name { get; set; }
    public int MasterId { get; set; }
    [ForeignKey("MasterId")]
    public virtual Master Master { get; set; }
}

This example uses EF attributes to aid in telling EF how to resolve the entity properties to respective tables and columns, as well as the relationship between Ingredient and Master. EF can work much of this out by convention, but it's good to understand and apply it explicitly because eventually you will come across situations where convention doesn't work as you expect.

Identifying the (Primary)Key and indicating it is an Identity column also tells EF to expect that the database will populate the PK automatically. (Highly recommended)

On the Master side we do something similar:

[Table("Masters")]
public class Master : Entity
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int MasterId { get; set; }
    public string Name { get; set; }
    [InverseProperty("Master")]
    public virtual ICollection<Ingredient> Ingredients { get; set; } = new List<Ingredient>();
}

Again we denote the Primary Key, and for our Ingredients collection, we tell EF what property on the other side (Ingredient) it should use to associate to this Master's list of Ingredients using the InverseProperty attribute.

Attributes are just one option to set up the relationships etc. The other options are to use configuration classes that implement IEntityConfiguration<TEntity> (EF Core), or to configure them as part of the OnModelCreating event in the DbContext. That last option I would only recommend for very small projects as it can start to become a bit of a God method quickly. You can split it up into calls to various private methods, but you may as well just use IEntityConfiguration classes then.

Now when you go to fetch Ingredients with it's Master, or a Master with its Ingredients:

using (var context = new AppDbContext())
{
    var ingredients = context.Ingredients
        .Include(x => x.Master)
        .Where(x => x.Master.Name.Contains("chicken"))
        .ToList();
    // or

    var masters = context.Master
        .Include(x => x.Ingredients)
        .Where(x => x.Name.Contains("chicken"))
        .ToList();

   // ...
}

Repository patterns are a more advanced concept that have a few good reasons to implement, but for the most part they are not necessary and an anti-pattern within EF implementations. I consider Generic repositories to always be an anti-pattern for EF implementations. Ie Repository<Ingredient> The main reason not to use repositories, especially Generic repositories with EF is that you are automatically increasing the complexity of your implementation and/or crippling the capabilities that EF can bring to your solution. As you see from working with your example, simply getting across an eager load through to the repository means writing in complex Expression<Func<TEntity>> parameters, and that just covers eager loading. Supporting projection, pagination, sorting, etc. adds even more boiler-plate complexity or limits your solution and performance without these capabilities that EF can provide out of the box.

Some good reasons to consider studying up on repository implementations /w EF:

  • Facilitate unit testing. (Repositories are easier to mock than DbContexts/DbSets)
  • Centralizing low-level data rules such as tenancy, soft deletes, and authorization.

Some bad (albeit very common) reasons to consider repositories:

  • Abstracting code from references or knowledge of the dependency on EF.
  • Abstracting the code so that EF could be substituted out.

Projecting to DTOs or ViewModels is an important aspect to building efficient and secure solutions with EF. It's not clear what "ObjectMapper" is, whether it is an Automapper Mapper instance or something else. I would highly recommend starting to grasp projection by using Linq's Select syntax to fill in a desired DTO from the models. The first key difference when using Projection properly is that when you project an object graph, you do not need to worry about eager loading related entities. Any related entity / property referenced in your projection ( Select ) will automatically be loaded as necessary. Later, if you want to leverage a tool like Automapper to help remove the clutter of Select statements, you will want to configure your mapping configuration then use Automapper's ProjectTo method rather than Map . ProjectTo works with EF's IQueryable implementation to resolve your mapping down to the SQL just like Select does, where Map would need to return everything eager loaded in order to populate related data. ProjectTo and Select can result in more efficient queries that can better take advantage of indexing than Eager Loading entire object graphs. (Less data over the wire between database and server/app) Map is still very useful such as scenarios where you want to copy values back from a DTO into a loaded entity.

Do it like this

public class Ingrident:Entity
{

    public string Name { get; set; }
    [ForeignKey(nameof(MasterId))]
    public Master Master { get; set; }
    public int MasterId { get; set; }

}

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