Using Generics with EF classes - c#

I'm using EF6 with Code First and have a few tables with virtually the same schema. I would like to be able to perform queries on these tables and return the results to a common object (class) rather than creating a new one for each.
So for example, EF won't allow:
public class Product1 {
public int id { get; set; }
public string name { get; set; }
}
public DbSet<Product1> Products1 { get; set; }
public DbSet<Product1> Products2 { get; set; }
So I have to define a second POCO:
public class Product1 {
public int id { get; set; }
public string name { get; set; }
}
public class Product2 {
public int id { get; set; }
public string name { get; set; }
}
public DbSet<Product1> Products1 { get; set; }
public DbSet<Product2> Products2 { get; set; }
At least I would like to be able to treat results from these POCOs the same so that I can plug results into another class:
public class SomeClass {
public <Product1 or Product2> Product { get; set; }
}
Be able to store the result from either db table in the same object:
SomeClass someclass = new SomeClass();
someclass.Product = _context.Products1.Where(p => p.id == 1).First();
or
someclass.Product = _context.Products2.Where(p => p.id == 1).First();
int thisId = someclass.Product.id;
How do I make someclass.Product generic so that it will accept either Product1 or Product2?

You would have to make the classes inherit from an interface and then use that interface in a generic type constraint.
public interface IProduct
{
int id { get; set; }
string name { get; set; }
}
public class Product1 : IProduct
{
public int id { get; set; }
public string name { get; set; }
}
public class Product2 : IProduct
{
public int id { get; set; }
public string name { get; set; }
}
Then you could define SomeClass as follows:
public class SomeClass<TProduct> where TProduct : IProduct
{
public TProduct Product { get; set; }
}

Related

How to fetch an attributes from my class with EF

I have 3 class, 2 are simple class and 1 combines the two of them.
public class Person:Base
{
public DateTime DOB { get; set; }
public string first { get; set; }
public string last { get; set; }
}
public class Roles:Base
{
public string Description { get; set; }
}
public class RolePerson:Base
{
public int pID { get; set; }
public virtual Person PersonID { get; set; }
public int rID { get; set; }
public virtual Roles RoleID { get; set; }
public int order { get; set; }
}
Now I have method that would select RolePerson witch would grab all member of pID with the id that i provide in the argument, and return the RoleID field.
this is how my method look like
public IEnumerable<Roles> PersonRoles(int personID)
{
return db.RolePerson.Where<RolePerson>((p) => p.pID == personID);
}
How can i say to return the role and not the roleperson.
Update:
My base class is as follow
public abstract class Base
{
public int ID { get; set; }
public Boolean isValid { get; set; }
public DateTime createdOn { get; set; }
public int createdID { get; set; }
public Person createdPerson { get; set; }
public DateTime updatedOn { get; set; }
public int updatedID { get; set; }
public Person updatedPerson { get; set; }
}
You can run a select statement in the end.
return d.RolePerson.Where(p => p.PId == personID).Select(r=> r.RoleID);

Custom one-to-many relationship in EF 6.1

Is it possible to write custom logic for a one-to-many relationship? Suppose I have the following classes:
public class ProductIncompatibility
{
[Key]
public int ProductIncompatibilityId { get; set; }
public int ProductIdA { get; set; }
public int ProductIdB { get; set; }
[ForeignKey("ProductIdA")]
public virtual Product ProductA { get; set; }
[ForeignKey("ProductIdB")]
public virtual Product ProductB { get; set; }
}
public class Product
{
[Key]
public int ProductId { get; set; }
public virtual ICollection<ProductIncompatibility> IncompatibleProducts { get; set; }
}
When I execute the following Linq-query:
Products.Select(it => new { it.ProductId, it.IncompatibleProducts })
or
Products.Include("IncompatibleProducts")
I would expect all ProductIncompatibility-records where ProductIdA = ProductId || ProductIdB = ProductId.
Is it possible to specify this logic, for example using the modelBuilder?

Product Site Class Structure Suggestion

I am creating a product website which have products and product categories, I have created the following classes:
public abstract class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public string ProductCode { get; set; }
public int ProductCatagoryId { get; set; }
}
public class DryFruits : Product
{
public decimal WeightInGrams { get; set; }
public decimal RatePerGram { get; set; }
}
public class DryFruitsPacks : Product
{
public string PackName { get; set; }
public decimal PackWeight { get; set; }
public decimal PackPrice { get; set; }
}
I want a method AddProduct(), which must be present in every class derived from product and adds that product to the database.
public class Product : IAddProduct
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public string ProductCode { get; set; }
public int ProductCatagoryId { get; set; }
public virtual void Add(Product p)
{
//Save to db
};
}
public class DryFruits : Product
{
public decimal WeightInGrams { get; set; }
public decimal RatePerGram { get; set; }
public override void Add(Product p)
{
//Save to db
}
}
public interface IAddProduct
{
void Add(Product product)
}
Public class SomeClass
{
Product product = new DryFruits()
{
ProductName = "Nut";
WeightInGrams = 0.01;
}
private IAddProduct _saveIt;
_saveIt.Add(product)
}
Public class SomeOtherClass
{
Product product = new Product()
{
ProductName = "Orange";
}
private IAddProduct _saveIt;
_saveIt.Add(product)
}
I always consider abstract classes to be a poor man's version of an interface. Even with the code above it fits the purpose of the SO's requirements.
You should add an abstract class to you base Product class. and override it in your derived classes.
public abstract class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public string ProductCode { get; set; }
public int ProductCatagoryId { get; set; }
public abstract void Save();
}
public class DryFruits : Product
{
public decimal WeightInGrams { get; set; }
public decimal RatePerGram { get; set; }
public override void Save()
{
//save the product
}
}
Derived classes. When you create a derived class like DryFruits or DryFruitsPacks , you must provide an override method for all abstract methods in the abstract class. The AddProduct() method in both derived classes satisfies this requirement.
Override
Int field. An abstract class can have an instance field in it. The derived classes can access this field through the base syntax..
Int
Cannot instantiate abstract class. The important part of an abstract class is that you can never use it separately from a derived class.
public abstract class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public string ProductCode { get; set; }
public int ProductCatagoryId { get; set; }
public abstract void AddProduct(params object[] arguments);
}
Abstract methods

Multiple collections of same type in entity framework

I have these two very simple classes.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Group Group { get; set; }
}
public class Group
{
public int Id {get;set;}
public ICollection<Person> Teachers { get; set; }
public ICollection<Person> Students { get; set; }
}
I would like EF to keep Teachers seperated from Students however they both get jumbled into a Person table with no way to distinguish between them.
Any ideas?
There are two ways to do it;
first : use a tag or enums in the Person object
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Group Group { get; set; }
public bool IsFaculty { get; set; }
}
or
public enum PersonType { Teacher, Student };
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Group Group { get; set; }
public PersonType PropPersonType { get; set; }
}
second : work object oriented with inheritance. This method has my preference because it's easy to manage and expand if you want to expand it.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Group Group { get; set; }
}
public class Student : Person
{
public int Year { get; set; }
// other student related fiels.
}
public class Teacher : Person
{
public List<Course> Courses { get; set; }
// other teacher related fields
}
Your Group is then
public class Group
{
public int Id {get;set;}
public ICollection<Teacher> Teachers { get; set; }
public ICollection<Student> Students { get; set; }
}
Separate in two different classes, and then inherit both of them with Person, because all the teachers and students are Persons, but not all Persons are teachers and students.
public class Person
{
}
public class Teacher : Person
{
}
public class Student : Person
{
}
I hope this helps
A little bit different case (when class is holding reference to two identical objects), but can be helpful:
public class Mission
{
//DB objects
public int Id { get; set; }
public int SourceLocationId {get; set}
public int DestinationLocationId {get; set}
//Virtual objects
public virtual Location SourceLocation { get; set; }
public virtual Location DestinationLocation { get; set; }
}
public class Location
{
//DB objects
public int Id {get;set;}
//Virtual objects
public virtual ICollection<Mission> SourceMissions { get; set; }
public virtual ICollection<Mission> DestinationMissions { get; set; }
}
Then all you need to do is bind it properly in OnModelCreating:
modelBuilder.Entity<Mission>()
.HasOptional(m => m.SourceLocation) //Optional or Required
.WithMany(sm => sm.SourceMissions)
.HasForeignKey(to => to.SourceLocationId);
modelBuilder.Entity<Mission>()
.HasOptional(m => m.DestinationLocation) //Optional or Required
.WithMany(sm => sm.DestinationMissions)
.HasForeignKey(to => to.DestinationLocationId);
I think you need some flag to distinguising them (I can't really belive you that can't). And after you can use TPH inheritance approach. See more info here and here.
To have Teachers and Students in two separate tables inherit from an abstract base:
public abstract class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Group Group { get; set; }
}
public class Student : Person
{
public int Year { get; set; }
// other student related fiels.
}
public class Teacher : Person
{
public List<Course> Courses { get; set; }
// other teacher related fields
}
public class Group
{
public int Id {get;set;}
public ICollection<Teacher> Teachers { get; set; }
public ICollection<Student> Students { get; set; }
}

Code first one to one select query

I try using Code First but I have some troubles, I think I forget or miss something ...
I have two pretty simple models : (a car have one FuelType, it's a one to one relation)
public class Car
{
public int CarID { get; set; }
public string CarName { get; set; }
public Fuel FuelType { get; set; }
...
}
public class Fuel
{
public int FuelID { get; set; }
public string Name{ get; set; }
...
}
My contextClass :
public class CarDbContext : DbContext
{
public DbSet<Car> Cars { get; set; }
public DbSet<Fuel> Fuels { get; set; }
public CarDbContext()
: base("CarDbConnectionString")
{
Database.SetInitializer<CarDbContext>(new CreateDatabaseIfNotExists<CarDbContext>());
}
And my main program :
class Program
{
static void Main(string[] args)
{
IList<Car> maListe;
using (var ctx = new CarDbContext())
{
var car = from c in ctx.Cars
select c;
maListe = car.ToList<Car>();
}
foreach (Car c in maListe)
Console.WriteLine("Car: {0} {1}", c.CarName, c.FuelType.Name);
Console.ReadLine();
}
}
In my console, I can see CarName but not the FuelType.Name of the car.
I tried few things like changing my query to this :
var car = from c in ctx.Cars.Include("Fuels")
select c;
But I get an exception :
A specified Include path is not valid. The Entity Type 'Car' does not declare a navigation property with the name 'Fuels'
Does someone can explain to me what I did wrong or what I forget ?
You have navigation property named FuelType:
var cars = ctx.Cars.Include("FuelType"); // not `Fuels`
If you want to 'eager-load' FuelType, try:
var car = from c in ctx.Cars.Include("FuelType")
Alternatively, you can mark it as virtual and have FuelType 'lazy-loaded' for you:
public class Car
{
public int CarID { get; set; }
public string CarName { get; set; }
public virtual Fuel FuelType { get; set; }
...
}
Are you sure that it's a one-to-one relation ? a FuelType is only associated with one car ?
Anyway, in order to make a one-to-one relation you have to include a navigation property for the Principal entity in your Dependent entity, as follow:
public class CarDbContext : DbContext
{
public CarDbContext()
: base("CarDbConnectionString")
{
Database.SetInitializer<CarDbContext>(new CreateDatabaseIfNotExists<CarDbContext>());
}
public DbSet<Car> Cars { get; set; }
public DbSet<Fuel> Fuels { get; set; }
}
public class Car
{
[Key]
public int CarID { get; set; }
public string CarName { get; set; }
public virtual Fuel FuelType { get; set; } // set as virtual
}
public class Fuel
{
[Key, ForeignKey("Car")] // primary and foreign key
public int FuelID { get; set; }
public string Name { get; set; }
public virtual Car Car { get; set; } // must include a navigation property for the Principal entity
}
But if it's a one-to-many relationship, you can handle it as follow:
public class Car
{
[Key]
public int CarID { get; set; }
public string CarName { get; set; }
public int FuelTypeID { get; set; }
[ForeignKey("FuelTypeID")]
public virtual Fuel FuelType { get; set; }
}
public class Fuel
{
public int FuelID { get; set; }
public string Name { get; set; }
}
public class CarDbContext : DbContext
{
public CarDbContext()
: base("CarDbConnectionString")
{
Database.SetInitializer<CarDbContext>(new CreateDatabaseIfNotExists<CarDbContext>());
}
public DbSet<Car> Cars { get; set; }
public DbSet<Fuel> Fuels { get; set; }
}
Thanks to all for answering me.
Your answers really make me progress in EF understanding.
By the way I was really stuck with the "System.Reflection.TargetInvocationException" Exception.
I changed so many things in my code than now it works but unfortunately I cannot understand why :(

Categories