I have a database where I want to store multiple items, every item would be unique, but they can have alternatives from other makers. So items would have a potential M:M relationship with each other.
For example, item A could have 3 alternatives. If I add item B, the alternative, the link for the alternative item should also be reversed, so that when I search item B, I would find item A as an alternative. When I add item C, it should be added as an alternative for both item A and B, and C should have as it's alternatives item A and B.
How's the best/smartest way to make this relationship between a class of its own kind?
The way I'm thinking is making a table in between called alternatives with a unique ID for each new item, if an alternative is added, then it's linked to that parent ID (alternative ID) and thus no new ID would be made in the alternative table. If it's established only later that this is an alternative, then remove the alternative ID of one of the two and add the item to the other alternative ID.
public partial class Item : Entity
{
public Item()
{
Id = GuidComb.GenerateComb();
}
public Guid Id { get; set; }
public string ItemName { get; set; }
public string MakerRef { get; set; }
public string Description { get; set; }
public virtual Maker Maker { get; set; }
public virtual IList<Offer> Offers { get; set; }
//stuck here on the smart way to make the relationship with itself?
}
Any advice would be welcome!
Thanks in advance!
The suggestion I'm thinking about might be similar to what you proposed, but I'll try to formulate it in database terms.
As far as I understand your requirements, your alternatives relationship will be completely transitive. This means your item set is partitioned in equivalence classes of subsets containing mutually alternative items. (If an item has no alternative yet, the subset consists of this item alone.)
If that's true, then the most elegant and redundancy free way to represent this is to choose one of the items of such as subset as a representative of the entire subset. This is reflected by the following table design:
item(id, equivalence_id, other attributes, ...)
where equivalence_id is a foreign key to the representative. Each item gets born with an equivalence id of null. If it is made equivalent to another item,
if the item already present has an equivalence id of null, assign the id of this representative to the equivalence it of both items,
if the item already present has a non-null equivalence id, assign this to the equivalence id of the new item.
Note that this works, in case there are many items in the same equivalence class, no matter which of this items are used to link the new one.
Example:
id equivalence_id name
1 1 abc
2 def
3 1 ghi
4 4 jkl
5 4 mno
This means abc and ghi are equivalent, as well as jkl and mno, but def isn't yet equivalent to anything. Now if pqr comes along and should become equivalent to abc, it would get equivalence id 1. The effect is the same as making it equivalent to ghi.
To find all items equivalent to a specific one, query
select *
from item
where equivalence_id = :my_equivalence_id
If some information pertaining to the equivalence class as a whole should be stored, a separate table for the equivalence classes only should be created.
Related
I know a lot has been written about that subject, so let me say first I carefully read first 2 pages on Google about this topic.
Many suggest to put DeleteBehavior.Restrict, and dotnet ef database update does not complain anymore. Problem solved until you try to delete record.
I have exactly the same problem as in this SO question, which is duplicate to this one. Proposed solution in second link is:
You are expected to break the cycle. You can do that by turning off
cascade delete (by including WillCascadeOnDelete(false) in the
respective relationship configuration) for at least one of the
relationships customers->payments or customers->billingCenters.
This is what I research so far.
Now let's get to the problem (again). I have diamond relationship:
All ForeignKeys must be not null. So setting one foreign key to allow null is not an option. I would also like that user can delete:
Parameter-Value-Parts -> No problem here
Parameter-Values -> No problem here
OnDelete(DeleteBehavior.Cascade)
Parameter-Parts -> No problem here
OnDelete(DeleteBehavior.Cascade)
Parameters -> Big problem
To follow answer on this SO question, I could break cycle between Parameter-Parts and Parameter-Value-Parts with OnDelete(DeleteBehavior.Restrict).
This would allow me to delete Parameter, but now I can not delete Parameter-Parts.
I could manually delete all Parameter-Value-Parts before deleting Parameter-Parts, but I would like to avoid manually deletion. Is that possible?
I also read on SO, that all cascade delete should be avoided in application and developer should manually take care of deleting dependent table rows before row deletion. What is consider best practice?
Delete cascade seems easy solution, but I am not searching for an easy solution but the right one. The one that would scale easily on large data model in large application.
I hope I understand your requirements correctly.
If you want a real diamond shape, then it would be required for ParameterValueParts to reference ParameterValues and ParameterParts that in turn reference the same Parameter. This would be modelled by composite keys in SQL:
class Parameter
{
public int ParameterId { get; set; }
}
class ParameterValue
{
// composite key of ParameterId, ValueId with ParameterId also being a foreign key
public int ParameterId { get; set; }
public int ValueId { get; set; }
}
class ParameterPart
{
// composite key of ParameterId, PartId with ParameterId also being a foreign key
public int ParameterId { get; set; }
public int PartId { get; set; }
}
class ParameterValueParts
{
// key
public int Id { get; set; }
// three foreign keys:
// ParameterId as foreign key to Parameter
// ParameterId, ValueId are the composite foreign key to ParameterValue
// ParameterId, PartId are the composite foreign key to ParameterPart
public int ParameterId { get; set; }
public int ValueId { get; set; }
public int PartId { get; set; }
}
This way, you can have many combinations of ParameterValue and ParameterPart but each combination is required to belong to a specific Parameter. With this basic design, I never had any cascade issues in my projects.
Side note: you can configure the ValueId and PartId as DatabaseGeneratedOption.Identity in their respective classes in order to not handle ID values manually. (at least in EF6, I hope EF Core works similar in this regard)
I have tried to make a logical data model, but I am not totally sure if it is modeled right. It is a very cut-down and basic model, but overall I want to know if it is modeled the way is should be.
Furthermore, how do I convert this into a class model in object oriented programming?
I guess I need:
Class Customer: int id, string name
Class Order: int id, string date, Customer object
Class Item: int id, string itemName, string item Desc
Class OrderItem: ?
For your data model, you don't need the relationship line between Orders and Items. You're using the junction table Order_Items to represent that many to many relationship.
As for the class models, you won't need a class to model the junction table. You can simply model it with a collection of Item in your Order class. The relationship between the Order and Item class is a composition relationship. You can think of it as: An Order has-a Item or an Order has-s collection of Item.
Here is how you can model the Order class in java.
public class Order {
private int id;
private Date date;
private Customer customer;
private List<Item> items; // you could use other collection types as well.
...
}
Edit:
Also for your many side of the relationships, you may consider using "one through many" line (crows foot with a line)" as opposed to "zero through many" (crows foot with circle). A order generally has atleast 1 item and atleast 1 customer. An order isn't an order without a customer or items.
I am about to implement a database for simple ecommerce platform. I want to implement the following:
Each product belongs to one product category;
Each product category has its own attributes;
Each product has one value for each attribute of this products type.
What relations should I use to store this kind of information?
Here is the logical model -- the way I understood it; you should be able to tweak it.
From this you can derive the physical model and the SQL code. The word KEY here means UNIQUE NOT NULL and you may use them for primary keys. Should you choose to introduce integers as primary keys, make sure you keep these UNIQUE.
Note that everything should be NOT NULL, once you get to the SQL.
Category named (CAT) exists.
Category {CAT}
KEY {CAT}
Attribute named (ATR) exists.
Attribute {ATR}
KEY {ATR}
Category (CAT) has attribute (ATR).
Each category has more than one attribute, it is possible for the same attribute to belong to more than one category.
CategoryAttribute {CAT, ATR}
KEY {CAT, ATR}
Product named (PRD) belongs to category (CAT).
Each product belongs to exactly one category, each category may have more than one product.
ProductCategory {PRD, CAT}
KEY {PRD}
KEY {PRD, CAT} -- seems redundant here, but is
-- needed for the FK from the next table
FOREIGN KEY {CAT} REFERENCES Category {CAT}
Product (PRD) from category (CAT) has attribute (ATR) that belongs to that category.
For each attribute that belongs to a category, that attribute may belong to more than one product from that category.
ProductCategoryAttribute {PRD, CAT, ATR}
KEY {PRD, CAT, ATR}
FOREIGN KEY {PRD, CAT} REFERENCES ProductCategory {PRD, CAT}
FOREIGN KEY {CAT, ATR} REFERENCES CategoryAttribute {CAT, ATR}
I don't know what database platform you are using, but for small numbers of products, and for queries that do not depend on the value of the per-category attributes, I'd use the following strategy:
CREATE TABLE "Category" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT
);
CREATE TABLE "Product" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"categoryId" INTEGER NOT NULL REFERENCES "Category" ("id"),
"attributes" TEXT NOT NULL
);
In this example, the categories are used mainly to enforce referential integrity and to provide a list of categories for navigation.
The attributes are stored inside the attributes column as JSON (most modern databases tend to support this natively).
If there are any attributes common to all types of products, we'd create specific columns in Product. For example, you could add creationDate, deletionDate, price, or whatnot.
This allows you to perform the typical Select * From Product Where id = #Id to get a specific product and Select * From Product Where categoryId = #CategoryId to get all products in a category.
A creationDate could be useful to sort the products by creation date and take the top N, if necessary, when filtering by category. However with small quantities like thousands of products you might as well get all products by category and do this in code.
Regarding the code aspect, products like Dapper have specific extensions helping you deal with these discriminated unions, but writing code to support it is fairly easy. Here's an how. I'll write pseudo-C#, but I'm sure you can adapt.
We have an abstract class taking care of the Product table rows
public abstract class ProductBase
{
// only the fields in the Product table here
public int CategoryId { get; set; }
protected string Attributes { get; set; }
// serialize extra fields to JSON in Attributes
protected abstract void Prepare();
// load the common fields from a data row
protected static ProductBase(DataRow dr)
{
CategoryId = int.Parse(dr["categoryId"]);
Attributes = dr["attributes"] as string;
}
// save to DB
public void Save()
{
Prepare();
// save to SQL
}
}
We also have specific classes per category which have the extra attributes and handle serialization and deserialization.
public class FooProduct: ProductBase
{
public string Color { get; set; }
protected override void Prepare()
{
Attributes = Json.Serialize(new { Color });
}
public FooProduct(DataRow dr): base(dr)
{
// we can only create foo products if the category is foo
if (CategoryId != 23) throw new InvalidOperationException();
var attr = Json.Deserialize(Attributes);
Color = attr.Color;
}
}
This idea works great while you don't need to get the "foo" products by Color. If you can afford to get all "foo" products and filter in code, great. If your database understands JSON and lets you query inside the Attributes field, good it will get slow with large numbers unless the server allows indexes to reference JSON-serialized values.
If all else fails, you'll need to create an index table which contains the color values and the ids of the products which have that color. This is relatively painful and you don't want to do it unless you need it (and you don't right now).
I have created a simple class and visual force page that displays a "group by". The output is perfect, it will display the number of opportunities a given account has:
lstAR = [ select Account.Name AccountName, AccountId, Count(CampaignID) CountResult from Opportunity where CampaignID != null group by Account.Name,AccountId having COUNT(CampaignID) > 0 LIMIT 500 ];
I would like to be able to say, if an account has more then 10 opportunities, then assign the opportunity to another account that has less then 10.
I used the following code to get the results in my visual force page:
public list<OppClass> getResults() {
list<OppClass> lstResult = new list<OppClass>();
for (AggregateResult ar: lstAR) {
oppClass objOppClass = new oppClass(ar);
lstResult.add(objOppClass);
}
return lstResult;
}
class oppClass {
public Integer CountResult { get;set; }
public String AccountName { get;set; }
public String AccountID { get;set; }
public oppClass(AggregateResult ar) {
//Note that ar returns objects as results, so you need type conversion here
CountResult = (Integer)ar.get('CountResult');
AccountName = (String)ar.get('AccountName');
AccountID = (String)ar.get('AccountID');
}
What would be the best approach to check the count greater then a given number and then assign an account with less then that given number the opportunities?
As I said, code wise I have a nice little controller and vf page that will display the account and count in a grid. Just not sure of a good approach to do the reassigning opportunity.
Thanks
Frank
I'm not sure why you'd be moving your opportunity to another account b/c typically the account is the organization/person buying the stuff?
But that said, ignoring the why and focusing on the how...
Trigger on the Opportunity, before insert
loop over trigger.new and count how many oppties you have per account (or owner) in that batch, put that into a map accountId to count [because you could be inserting 10 oppties for the same account!]. If ever your count is > 10 change the assignment using whatever assignment helper class you have.
Also populate set of accountIds.
Then run your aggregate for each account where Id in set of accountIds, you'll have to group by AccountId.
Loop over results, and update the map of accountId to count.
Then loop over trigger.new and for each oppty, look up in the map by accountId the count. If the count > 10 then do your assignment using your helper class.
And done.
Of course your assignment helper class is another issue to tackle - how do you know which account/user to assign the opportunity to, are you going to use queues, custom objects, custom settings to govern the rules, etc...
But the concept above should work...
in an app i have an entity that contains a list of other entities (let's say an event holding a list of assigned employees)
using objectify - i need to find all the events a particular employee is assigned to.
is there a basic way to filter a query if it contains the parameter - kind of the opposite of the query in
... quick pseudocode
findAll(Employee employee) {
...
return ofy.query(Event.class).filter("employees.contains", employee).list();
}
any help would be greatly appreciated
i tried just doing filter("employees", employee) after seeing this http://groups.google.com/group/objectify-appengine/browse_thread/thread/77ba676192c08e20 - but unfortunately this returns me an empty list
currently i'm doing something really inefficient - going through each event, iterating through the employees and adding them to a new list if it contains the given employee just to have something that works - i know this is not right though
let me add one thing,
the above query is not actually what it is, i was just using that because i did not think this would make a difference.
The Employee and Events are in the same entity group with Business as a parent
the actual query i am using is the following
ofy.query(Event.class).ancestor(businessKey).filter("employees", employee).list();
unfortunately this is still returning an empty list - does having the ancestor(key) in there mess up the filter?
solution, the employees field was not indexed correctly.
I added the datastore-indexes file to create a composite index, but was testing originally on a value that I added before the employees field was indexed, this was something stupid i was doing - simply having an index on the "business" field and the "employees" field fixed everything. the datastore-indexes file did not appear to be necessary, after deleting it and trying again everything worked fine.
Generally, you do this one of two ways:
Put a property of Set<Key<Employee>> on the Event
or
Put a property of Set<Key<Event>> on the Employee
You could also create a relationship entity, but if you're just doing filtering on values with relatively low counts, usually it's easier to just put the set property on one entity or the other.
Then filter as you describe:
ofy.query(Event.class).filter("employees", employee).list()
or
ofy.query(Employee.class).filter("events", event).list()
The list property should hold a Keys to the target entity. If you pass in an entity to the filter() method, Objectify will understand that you want to filter by the key instead.
Example :
/***************************************************/
#Entity
#Cache
public class News {
#Id Long id;
String news ;
#Index List<Long> friend_list = new ArrayList<Long>();
// My friends who can see my news , exemele : friend_list.add(id_f1); friend_list.add(id_f2); friend_list.add(id_f3);
//To make an operation on "friend_list", it is obligatory to index it
}
/*************************************************/
public News(Long id_f){
List<Long> friend_id = new ArrayList<Long>();
friend_id.add(id_f);
Query<Nesw> query = ofy().load().type(News.class).filter("friend_list in",friend_id).limit(limit);
//To filter a list, just after the name of the field you want to filter, add "IN".
//here ==> .filter("friend_list in",friend_id);
// if friend_list contains "id_friend" ==> the query return value
.........
}