Subsonic one to many relationship child objects - c#

I don't know if I am doing something wrong "I think I am" or I have hit a subsonic limitation.
I have 3 tables Articles, ArticleCategories and ArticleComments with a one to many relationship between Articles the other tables.
I have created the following class
public partial class Article
{
private string _CategoryName;
public string CategoryName
{
get { return _CategoryName; }
set
{
_CategoryName = value;
}
}
public ArticleCategory Category { get;set;}
private List<System.Linq.IQueryable<ArticleComment>> _Comments;
public List<System.Linq.IQueryable<ArticleComment>> Comments
{
get
{
if (_Comments == null)
_Comments = new List<IQueryable<ArticleComment>>();
return _Comments;
}
set
{
_Comments = value;
}
}
}
I get a collection of articles using this snippet
var list = new IMBDB().Select.From<Article>()
.InnerJoin<ArticleCategory>(ArticlesTable.CategoryIDColumn, ArticleCategoriesTable.IDColumn)
.InnerJoin<ArticleComment>(ArticlesTable.ArticleIDColumn,ArticleCommentsTable.ArticleIDColumn)
.Where(ArticleCategoriesTable.DescriptionColumn).IsEqualTo(category).ExecuteTypedList<Article>();
list.ForEach(x=>x.CategoryName=category);
list.ForEach(y => y.Comments.AddRange(list.Select(z => z.ArticleComments)));
I get the collection Ok but when I try to use the comments collection using
foreach (IMB.Data.Article item in Model)
{
%>
<%
foreach (IMB.Data.ArticleComment comment in item.Comments)
{
%>
***<%=comment.Comment %>***
<%}
} %>
the comment.Comment line throws this exception
Unable to cast object of type 'SubSonic.Linq.Structure.Query`1[IMB.Data.ArticleComment]' to type 'IMB.Data.ArticleComment'.
I am basically trying to avoid hitting the database each time the comment is needed.
is there another to achieve this?
Thanks

OK - I'm not doing exactly what you're attempting but hopefully this example will bring some clarity to the situation. This is more along the lines of how I would do a join using SubSonic if I absolutely had to do it this way. The only way I would consider this approach is if I were confined by some 3rd party implementation of the object and/or database schema...
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using SubSonic.Repository;
namespace SubsonicOneToManyRelationshipChildObjects
{
public static class Program
{
private static readonly SimpleRepository Repository;
static Program()
{
try
{
Repository = new SimpleRepository("SubsonicOneToManyRelationshipChildObjects.Properties.Settings.StackOverflow", SimpleRepositoryOptions.RunMigrations);
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.ReadLine();
}
}
public class Article
{
public int Id { get; set; }
public string Name { get; set; }
private ArticleCategory category;
public ArticleCategory Category
{
get { return category ?? (category = Repository.Single<ArticleCategory>(single => single.Id == ArticleCategoryId)); }
}
public int ArticleCategoryId { get; set; }
private List<ArticleComment> comments;
public List<ArticleComment> Comments
{
get { return comments ?? (comments = Repository.Find<ArticleComment>(comment => comment.ArticleId == Id).ToList()); }
}
}
public class ArticleCategory
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ArticleComment
{
public int Id { get; set; }
public string Name { get; set; }
public string Body { get; set; }
public int ArticleId { get; set; }
}
public static void Main(string[] args)
{
try
{
// generate database schema
Repository.Single<ArticleCategory>(entity => entity.Name == "Schema Update");
Repository.Single<ArticleComment>(entity => entity.Name == "Schema Update");
Repository.Single<Article>(entity => entity.Name == "Schema Update");
var category1 = new ArticleCategory { Name = "ArticleCategory 1"};
var category2 = new ArticleCategory { Name = "ArticleCategory 2"};
var category3 = new ArticleCategory { Name = "ArticleCategory 3"};
// clear/populate the database
Repository.DeleteMany((ArticleCategory entity) => true);
var cat1Id = Convert.ToInt32(Repository.Add(category1));
var cat2Id = Convert.ToInt32(Repository.Add(category2));
var cat3Id = Convert.ToInt32(Repository.Add(category3));
Repository.DeleteMany((Article entity) => true);
var article1 = new Article { Name = "Article 1", ArticleCategoryId = cat1Id };
var article2 = new Article { Name = "Article 2", ArticleCategoryId = cat2Id };
var article3 = new Article { Name = "Article 3", ArticleCategoryId = cat3Id };
var art1Id = Convert.ToInt32(Repository.Add(article1));
var art2Id = Convert.ToInt32(Repository.Add(article2));
var art3Id = Convert.ToInt32(Repository.Add(article3));
Repository.DeleteMany((ArticleComment entity) => true);
var comment1 = new ArticleComment { Body = "This is comment 1", Name = "Comment1", ArticleId = art1Id };
var comment2 = new ArticleComment { Body = "This is comment 2", Name = "Comment2", ArticleId = art1Id };
var comment3 = new ArticleComment { Body = "This is comment 3", Name = "Comment3", ArticleId = art1Id };
var comment4 = new ArticleComment { Body = "This is comment 4", Name = "Comment4", ArticleId = art2Id };
var comment5 = new ArticleComment { Body = "This is comment 5", Name = "Comment5", ArticleId = art2Id };
var comment6 = new ArticleComment { Body = "This is comment 6", Name = "Comment6", ArticleId = art2Id };
var comment7 = new ArticleComment { Body = "This is comment 7", Name = "Comment7", ArticleId = art3Id };
var comment8 = new ArticleComment { Body = "This is comment 8", Name = "Comment8", ArticleId = art3Id };
var comment9 = new ArticleComment { Body = "This is comment 9", Name = "Comment9", ArticleId = art3Id };
Repository.Add(comment1);
Repository.Add(comment2);
Repository.Add(comment3);
Repository.Add(comment4);
Repository.Add(comment5);
Repository.Add(comment6);
Repository.Add(comment7);
Repository.Add(comment8);
Repository.Add(comment9);
// verify the database generation
Debug.Assert(Repository.All<Article>().Count() == 3);
Debug.Assert(Repository.All<ArticleCategory>().Count() == 3);
Debug.Assert(Repository.All<ArticleComment>().Count() == 9);
// fetch a master list of articles from the database
var articles =
(from article in Repository.All<Article>()
join category in Repository.All<ArticleCategory>()
on article.ArticleCategoryId equals category.Id
join comment in Repository.All<ArticleComment>()
on article.Id equals comment.ArticleId
select article)
.Distinct()
.ToList();
foreach (var article in articles)
{
Console.WriteLine(article.Name + " ID " + article.Id);
Console.WriteLine("\t" + article.Category.Name + " ID " + article.Category.Id);
foreach (var articleComment in article.Comments)
{
Console.WriteLine("\t\t" + articleComment.Name + " ID " + articleComment.Id);
Console.WriteLine("\t\t\t" + articleComment.Body);
}
}
// OUTPUT (ID will vary as autoincrement SQL index
//Article 1 ID 28
// ArticleCategory 1 ID 41
// Comment1 ID 100
// This is comment 1
// Comment2 ID 101
// This is comment 2
// Comment3 ID 102
// This is comment 3
//Article 2 ID 29
// ArticleCategory 2 ID 42
// Comment4 ID 103
// This is comment 4
// Comment5 ID 104
// This is comment 5
// Comment6 ID 105
// This is comment 6
//Article 3 ID 30
// ArticleCategory 3 ID 43
// Comment7 ID 106
// This is comment 7
// Comment8 ID 107
// This is comment 8
// Comment9 ID 108
// This is comment 9
Console.ReadLine();
// BETTER WAY (imho)...(joins aren't needed thus freeing up SQL overhead)
// fetch a master list of articles from the database
articles = Repository.All<Article>().ToList();
foreach (var article in articles)
{
Console.WriteLine(article.Name + " ID " + article.Id);
Console.WriteLine("\t" + article.Category.Name + " ID " + article.Category.Id);
foreach (var articleComment in article.Comments)
{
Console.WriteLine("\t\t" + articleComment.Name + " ID " + articleComment.Id);
Console.WriteLine("\t\t\t" + articleComment.Body);
}
}
Console.ReadLine();
// OUTPUT should be identicle
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.ReadLine();
}
}
}
}
The following is my personal opinion and conjecture and can be tossed/used as you see fit...
If you look at the "Better way" example this is more along the lines of how I actually use SubSonic.
SubSonic is based on some simple principles such as
Each table is an object (class)
Each object instance has an Id (e.g. Article.Id)
Each object should have a Name (or similar)
Now if you write your data entities (your classes that are representations of your tables) in a manner that makes sense when you use SubSonic you're going to work well together as a team. I don't really do any joins when I work with SubSonic because I typically don't need to and I don't want the overhead. You start to show a good practice of "lazy-loading" the comment list property on your article object. This is good, this means if we need the comments in the consumer code, go get-em. If we don't need the comments, don't spend the time & money to go fetch them from the database. I restructured your ArticleCategory to Article relationship in a way that makes sense to me but may not suit your needs. It seemed like you agreed with Rob conceptually (and I agree with him again).
Now, there are 1000 other improvements to be made to this architecture. The first that comes to mind is to implement a decent caching pattern. For example, you may not want to fetch the comments from the database on each article, every time the article is loaded. So you might want to cache the article and comments and if a comment is added, in your "add comment" code, wipe the article from the cache so it gets rebuild next load. Categories is a perfect example of this... I would typically load something like categories (something that isn't likely to change every 5 minutes) into a master Dictionary (int being the category id) and just reference that in-memory list from my Article code. These are just basic ideas and the concept of caching, relational mapping database or otherwise can get as complicated as you like. I just personally try to adhere to the SubSonic mentality that makes database generation and manipulation so much easier.
Note: If you take a look at the way Linq2SQL does things this approach is very similar at the most basic layer. Linq2SQL typically loads your dependent relationships every time whether you wanted that or knew it was doing it or not. I much prefer the SubSonic "obviousness", if you will, of what is actually happening.
Sorry for the rant but I really hope you can run the above code in a little Console application and get a feel for what I'm getting at.

first thought, shouldn't the category association be the other way around? Category has many Articles? I don't know if that will help here - it's my first observation.
I think you're having a naming collision here. A "Structure.Query" is an object we create on the Structs.tt and it looks like that's the namespace you're referencing somehow with "item.Comments". My guess is there's a table somewhere called "Comments" that is getting confused.
Can you open up the Article class and look at the return of "Comments" property to make sure it's returning a type, and not the query?

Related

Verifying multiple product's features added in the cart is same in the Order Summary page with Selenium webdriver + Specflow + C#+ page object pattern

I have added 2 products in my basket. in the first step of my Test.
In the last step I assert that same product that were added in the first step of the test comes in the Last step which is the "Order Summary page". Please find below the code and screen shots.
Here there are 2 items, all the features of the 2 items displayed have same classes. just the indexing of the div is different. rest is the same.
I am using the Scenario Context functionality of the Specflow.
Mindwell, i want to achieve like this image , i have code currently for only 1 product, and i want to do the same for multiple products.
1) Basketpage. In this step i take all the elements of the page and take their values in the scenario context.
string productname = pdpPage.getBrandName();
pdpPage.ExpandSideBar();
pdpPage.SelectProductQuantity(Quantity);
var hp = new HeaderPage(driver);
int currentBagQuantity = hp.getBagQuantity();
decimal currentTotalBagPrice = hp.getBagTotalPrice();
ScenarioContext.Current.Add("Product Name",productname);
ScenarioContext.Current.Add("QuantityAdded", int.Parse(Quantity));
ScenarioContext.Current.Add("BagQuantity", currentBagQuantity);
ScenarioContext.Current.Add("CurrentBagPrice", currentTotalBagPrice);
ScenarioContext.Current.Add("ProductPrice", pdpPage.getProductPriceInDecimal());
2) OrderSummary Page. In this step i assert the values , This is the order summary page.
var os = new OrderSummaryPage(driver);
string brandname = os.getOrderProductName();
int quantity = os.getOrderQuantity();
decimal price = os.getOrderPrice();
Assert.IsTrue(brandname.Equals((string)ScenarioContext.Current["Product Name"]), "Err! Product is different!, on pdp is :" + ScenarioContext.Current["Product Name"] + "on order summary is" + brandname);
Assert.IsTrue(quantity.Equals((int)ScenarioContext.Current["QuantityAdded"]), "Err! Quantity is different from ordered!");
Assert.IsTrue(price.Equals((decimal)ScenarioContext.Current["ProductPrice"]), "Err! Product price is appearing to be different!");
Assert.IsTrue(GenericFunctions.isElementPresent(os.Delivery_Address), "Delivery Address details are not present");
Assert.IsTrue(GenericFunctions.isElementPresent(os.Billing_Address), "Billing Address details are not present!!");
I am new to this stuff!! How to loop these and get the dynamic stuff. I want to check and verify each items Product name , price , quantity.
Doing this :
My Step File :
[When(#"I check the items on basket page")]
public void WhenICheckTheItemsOnBasketPage()
{
var bp = new BasketPage(driver);
var h = bp.getLISTItemsFromOrderPage();
for (int i = 0; i <= h.Count; i++)
{
ScenarioContext.Current.Add("item", h[i]);
}
}
BasketPage.cs
public IList getLISTItemsFromOrderPage()
{
List<BasketItems> orderProducts = new List<BasketItems>();
var elements = (driver.FindElements(By.Id("basketitem")));
foreach (IWebElement element in elements)
{
orderProducts.Add(CreateOrderProduct(element));
}
return orderProducts;
}
public BasketItems CreateOrderProduct(IWebElement item)
{
return new BasketItems()
{
BrandName = item.FindElement(By.TagName("a")).Text.Trim(),
Quantity = GenericFunctions.DropDown_GetCurrentValue(item.FindElement(By.TagName("select"))),
Price = Convert.ToDecimal(item.FindElement(By.ClassName("col-md-2")).Text.Substring(1))
};
}
BasketItem.cs
public class BasketItems : BasketPageOR
{
public string BrandName { get; set; }
public string Quantity { get; set; }
public decimal Price { get; set; }
}
Please Help! Thanks in Advance!!
you method os.getOrderProductName(); doesn't make any sense if an order can have multiple products.
You should have a method os.getOrderProducts(); which will return a collection of OrderProduct objects. It should do this by finding all elements which have an id="productorderelement" (although you should not have elements with the same id, you should really use a class for this) and then loop over each element extracting the information to build the OrderProduct soemthing like this should allow you to get the elements with the id:
List<OrderProduct> orderProducts = new List<OrderProduct>();
var elements = (Driver.FindElements(By.XPath("//*[#id=\"productorderelement\"]")))
foreach (element in elements)
{
orderProducts.Add(CreateOrderProduct(element));
}
public class OrderProduct
{
public string BrandName{get;set;}
public int Quantity{get;set;}
public double Price{get;set;}
}
public OrderProduct CreateOrderProduct(IWebElement element)
{
return new OrderProduct()
{
BrandName= element.Something, //you need to extract the appropriate bit of the webelement that holds the brandname, quantity and price, but you don't show us the structure so I can't help there
Quantity= element.FindElement(By.Class("quantity")).Text, //for example
Price= element.GetAttribute("Price") //again another example
}
}

How to group by records in c#

I am trying to use Group By method supported by LINQ.
I have this class
public class Attribute
{
public int Id {get;set;}
public string Name {get;set;}
public string Value {get;set;}
}
I have a service method that will retrive a IList
var attributes = _service.GetAll();
Id Name Value
7 Color Black
7 Color White
220 Size 16
Now I have another tow classes
one is
public class AttributeResourceModelSubItem
{
public string Name {get;set;}
public List<AttributeValueResourceModelSubItem> values { get; set; }
}
public class AttributeValueResourceModelSubItem
{
public int Id;
public string Name {get;set;}
}
I am trying to loop through the attributes list. and if the attribute id is the same, I wanna insert the records where id = to that id inside the AttributeValueResourceModelSubItem in which id = 1 and Name will be equal to the attribute value.
This what I got so far.
private IList<AttributeResourceModelSubItem> FormatAttributes(IList<Attribute> attributes)
{
Dictionary<int, Attribute> baseTypes = new Dictionary<int, Attribute>();
AttributeResourceModelSubItem attributeResourceModelSubItem = null;
var list = new IList<AttributeResourceModelSubItem>();
foreach (var item in attributes)
{
if (!baseTypes.ContainsKey(item.Id))
{
attributeResourceModelSubItem = new AttributeResourceModelSubItem()
attributeResourceModelSubItem.key = item.Name;
attributeResourceModelSubItem.values.Add(new AttributeValueResourceModelSubItem()
{
id = 1,
name = item.Value
});
list.Add(attributeResourceModelSubItem);
}
baseTypes.Add(item.Id, item);
}
return list;
}
Any help is appreciated.
It's pretty unclear from your example what you're actually trying to do, but this is the gist I get.
private IEnumerable<AttributeResourceModelSubItem> FormatAttributes(IEnumerable<Attribute> attributes)
{
return attributes.GroupBy(c => c.Id)
.Select(c => new AttributeResourceModelSubItem()
{
key = c.First().Name,
values = c.Select(x => new AttributeValueResourceModelSubItem()
{
id = 1,
name = x.value
}).ToList();
});
}
You should also definitely not use the word Attribute as a class name. That's already a .NET class.
I'll admit that I don't quite understand the id = 1 part, but I took that from your code. It also seems odd to group by the id then try and take the first name, but again that's what you have.
If you do, in fact, want to group by the name and take the id, which makes a little more sense, you'll want to swap a couple things around. Admittedly this structure still seems a little odd to me, but hopefully this will get you a couple steps closer to your goal.
private IEnumerable<AttributeResourceModelSubItem> FormatAttributes(IEnumerable<Attribute> attributes)
{
return attributes.GroupBy(c => c.name)
.Select(c => new AttributeResourceModelSubItem()
{
key = c.Key,
values = c.Select((item, index) => new AttributeValueResourceModelSubItem()
{
id = index + 1,
name = item.value
}).ToList();
});
}
I also made your id = 1 increment starting at one for each element in each values list. You might want that to be item.Id, or even just your original 1.

Generating Sample Data

I am working on a school project using ASP.NET MCV4, EF6, Code-First models. Now, I am wondering how should I fill the database with sample data. I checked migrations, but I don't want to mess with the db structure. I just want to insert data, I tried this:
namespace Autokereskedes.Models
{
public class _SampleData : DropCreateDatabaseIfModelChanges<AutoDb>
{
protected override void Seed(AutoDb context)
{
new Autokereskedes.Models.SampleData.Users().List().ForEach(u=>context.Users.Add(u));
new Autokereskedes.Models.SampleData.Cars().List().ForEach(c => context.Cars.Add(c));
}
}
}
namespace Autokereskedes.Models.SampleData
{
public class Users
{
public List<User> List()
{
var crypto = new SimpleCrypto.PBKDF2();
var salt = Autokereskedes.Controllers.AccountController.PasswordSalt;
return new List<User>
{
new User {
UserId = Guid.NewGuid(),
Email = "admin#autoker.hu",
Password = crypto.Compute("admin",salt),
Phone = "+36 20 XXX YYZZ",
Banned = false,
Country = "Hungary",
City = "Szeged",
Street = "DivisonByZero street 1/0",
ZipCode = 1100,
RegistrationDate = DateTime.Now
},
new User {
UserId = Guid.NewGuid(),
Email = "user#autoker.hu",
Password = crypto.Compute("user",salt),
Phone = "+36 20 XXX YYZZ",
Banned = false,
Country = "Hungary",
City = "Szeged",
Street = "DivisonByZero street 2/0",
ZipCode = 1100,
RegistrationDate = DateTime.Now
}
};
}
}
}
It is working, I thought. But how should I insert data that has foreign keys? I saw a tutorial where they used a single file for all the List<>-s and in the foreign key field used something like this: Genre = genres.Single(g => g.Name == "Jazz"). I can't really copy that now.
namespace Autokereskedes.Models.SampleData
{
public class Cars
{
public List<Car> List()
{
return new List<Car>
{
new Car {
CarId = Guid.NewGuid(),
DepoId = now what
},
new Car {
}
};
}
}
}
When you seed the data, you need to account for the foreign key relationships... eventually.
return new List<Car>
{
new Car {
CarId = Guid.NewGuid(),
DepoId = now what // it depends, is this a required relationship?
},
new Car {
}
};
If DepoId is an optional relationship, you can just wait until you have a Depo & DepoId before setting up this property. Otherwise if it is required, you need to set it up before you insert it into the context.
protected override void Seed(AutoDb context)
{
new Autokereskedes.Models.SampleData.Users().List()
.ForEach(u=>context.Users.Add(u));
var cars = new Autokereskedes.Models.SampleData.Cars().List();
var depos = new Autokereskedes.Models.SampleData.Depos().List();
foreach (var car in cars)
{
car.DepoId = depos.FirstOrDefault(x => x.DepoId == ...?);
context.Cars.Add(car);
}
}
I suppose the question is, how do you decide which depo should be assigned to each car?
Another way to do it, since your Depo entities are a dependency and you need to resolve them first, would be to pass your depos to your cars list method:
var depos = new Autokereskedes.Models.SampleData.Depos().List();
depos.ForEach(d => context.Depos.Add(d));
//context.SaveChanges(); no need for this since your id's are Guid's
var cars = new Autokereskedes.Models.SampleData.Cars().List(depos);
public List<Car> List(IEnumerable<Depo> depos)
{
// now you have depos to look for id's in
return new List<Car>
{
new Car {
CarId = Guid.NewGuid(),
DepoId = depos.SingleOrDefault(x => [your predicate]),
},
new Car {
}
};
}
The process is called "seeding" the data.
You create an initializer object and override the Seed method:
public class MyDataContextDbInitializer : DropCreateDatabaseIfModelChanges<MyDataContext>
{
protected override void Seed(MagazineContext context)
{
context.Customers.Add(new Customer() { CustomerName = "Test Customer" });
}
}
Here is an article describing how to do it:
http://www.codeproject.com/Articles/315708/Entity-Framework-Code-First-Data-Initializers
I made an Nuget package, so you can generate random contact data (firstname, lastname, city, zipcode, birthdates, accountnumbers, etc)
In this version you can export to CSV, XML, JSON and SQL.
Open Visual Studio, Manage Nuget Packages
Search online for:
DSharp FrameWork: ContactGenerator
Any suggestions for extending functionality are welcome.

How can I use EF to add multiple child entities to an object when the child has an identity key?

We are using EF5 and SQL Server 2012 the following two classes:
public class Question
{
public Question()
{
this.Answers = new List<Answer>();
}
public int QuestionId { get; set; }
public string Title { get; set; }
public virtual ICollection<Answer> Answers { get; set; }
}
public class Answer
{
public int AnswerId { get; set; }
public string Text { get; set; }
public int QuestionId { get; set; }
public virtual Question Question { get; set; }
}
Mapping is as follows:
public class AnswerMap : EntityTypeConfiguration<Answer>
{
public AnswerMap()
{
// Primary Key
this.HasKey(t => t.AnswerId);
// Identity
this.Property(t => t.AnswerId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Database DDL
CREATE TABLE Answer (
[AnswerId] INT IDENTITY (1, 1) NOT NULL,
[QuestionId] INT NOT NULL,
[Text] NVARCHAR(1000),
CONSTRAINT [PK_Answer] PRIMARY KEY CLUSTERED ([AnswerId] ASC)
)";
Here are the results of what I have tried:
This works for one child:
var a = new Answer{
Text = "AA",
QuestionId = 14
};
question.Answers.Add(a);
_uow.Questions.Update(question);
_uow.Commit();
This does not work for more than one child:
Error: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
var a = new Answer{
AnswerId = 0,
Text = "AAA",
QuestionId = 14
};
var b = new Answer {
AnswerId = 0,
Text = "BBB",
QuestionId = 14
};
question.Answers.Add(a);
question.Answers.Add(b);
_uow.Questions.Update(question);
_uow.Commit();
This does not work for more than one child:
It creates AnswerID's 1000 and 1001 but I want new Id's to be created by the database.
var a = new Answer{
AnswerId = 1000,
Text = "AAA",
QuestionId = 14
};
var b = new Answer {
AnswerId = 1001,
Text = "BBB",
QuestionId = 14
};
question.Answers.Add(a);
question.Answers.Add(b);
_uow.Questions.Update(question);
_uow.Commit();
Does not work:
Compiler error. Can't convert null to int
var a = new Answer{
AnswerId = null,
Text = "AAA",
QuestionId = 14
};
var b = new Answer
{
AnswerId = null,
Text = "BBB",
QuestionId = 14
};
question.Answers.Add(a);
question.Answers.Add(b);
_uow.Questions.Update(question);
_uow.Commit();
Doesn't work:
ObjectStateManager cannot track multiple objects with the same key.
var a = new Answer{
Text = "AAA",
QuestionId = 14
};
var b = new Answer
{
Text = "BBB",
QuestionId = 14
};
question.Answers.Add(a);
question.Answers.Add(b);
_uow.Questions.Update(question);
_uow.Commit();
In my application I have one or more new Answer objects generated on the client and then these are sent to the server. Above I am simulating what will happen without adding the client into the question. Note that the adding of all Answers to the Question object is done on the client and then comes over in a JSON string to the server. It is then deserialized to a Question Object like this:
public HttpResponseMessage PutQuestion(int id, Question question) {
_uow.Questions.Update(question);
_uow.Commit();
I want each Answer objects to be created with a new identity ID, for these to be added to the Question object and for the Question object to be returned back in the normal way.
I don't know how this can be done. All my simple tests so far don't work. Please note this is a variation on an earlier question by our group member which was less clear and which I am trying to close. This question is I hope more clear.
Notes:
Here is the way update is coded:
public virtual void Update(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State == EntityState.Detached)
{
DbSet.Attach(entity);
}
dbEntityEntry.State = EntityState.Modified;
}
I have run into the same identity "limitation" as well. It turns out that if you add a parent and any children, EF can handle the fact that the parent and children are all being added together. You run into problems when you Update the parent and insert two children at the same time. If you attach the parent, EF will automatically pick up those two children and attach them whether you want it to or not. Since we want it to auto generate the Id, we wouldn't set the primary key of the children. However, EF cannot handle items with the same Primary key when the parent is an Update and blows up since both have the same PK of 0 for both children.
The only way I have found around this is to manually set the ids of the children to different numbers. I usually set the first child's Id to -1, then -2 for the second child, and so on. This will cause EF to save the children and the key will automatically be updated due to the Identity running on the database because -1 and -2 are not valid identity values.
However, this will cause great pain if you have a 3rd level or beyond. Not only do you have to update this PK on each child, but then you'd have to update the FK on any of its children to this new -1 or -2 value. Otherwise, the save will fail again!
The only other option I see is really just to insert one child at a time and call save so the context isn't dealing with more than one object with the same PK, but that kind of defeats the purpose of an ORM...
Did you mentioned that you are adding a two times...?!
question.Answers.Add(a);
question.Answers.Add(a);
Usually, to add items which their id is identity, you must skip setting the id. You also should add the [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)] attribute to these IDs:
public class Answer
{
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int AnswerId { get; set; }
public string Text { get; set; }
public int QuestionId { get; set; }
public virtual Question Question { get; set; }
}
And add data like this:
var a = new Answer{
Text = "AAA",
QuestionId = 14
};
var b = new Answer
{
Text = "BBB",
QuestionId = 14
};
dbContext.Answers.Add(a);
dbContext.Answers.Add(b);
dbContext.SaveChanges();
// ...
Try these things:
use the Create() method of DbSet
add the new instances to the Answers collection of your Context
You have set the QuestionId appropriately for EF to realise the relationship. Also, do not explicitly set AnswerId to zero.
var a = new _uow.Answers.Create();
a.Text = "AAA";
a.QuestionId = 14;
_uow.Answers.Add(a);
var b = new _uow.Answers.Create();
b.Text = "BBB";
b.QuestionId = 14;
_uow.Answers.Add(a);
You may need to make a call to _uow.ChangeTracker.DetectChanges() if you plan on querying the Answers collection of Question 14
If you have correctly declared the Id as Key and as being DBGenerated identity.
Then EF will allow you ADD many of these to the context before saving.
You can NOT ATTACH items with the same key.
Attach is meant for offline data, put in context, set its state and save type scenarios.
You have either used the Same instance twice and with EF tracking by default caused a mess.
Or somehow used ATTACH twice. Make sure you are handling your instances cleanly.*
eg
public class BaseEntityLongConfiguration<T> : EntityTypeConfiguration<T> where T : BaseObjectLong {
public BaseEntityLongConfiguration(DatabaseGeneratedOption DGO = DatabaseGeneratedOption.Identity) {
// Primary Key
this.HasKey(t => t.Id);
// Properties
//Id is an indent allocated by DB
this.Property(t => t.Id).HasDatabaseGeneratedOption(DGO); // default to db generated
this.Property(t => t.RowVersion) // for concurrency
.IsRequired()
.IsFixedLength()
.HasMaxLength(8)
.IsRowVersion();
}
}
A just tried a simple Test to check it works (in ef5)
public class ExampleLog {
public virtual long Id { get; set; }
public virtual string MessageText { get; set; }
}
[TestMethod]
public void ExampleLogTest() {
var e1 = new ExampleLog();
e1.MessageText = "example1";
var e2 = new ExampleLog();
e2.MessageText = "example2";
_context.Set<ExampleLog>().Add(e1);
_context.Set<ExampleLog>().Add(e2);
var res = _context.SaveChanges();
Debug.WriteLine("result expected 2->" + res.ToString());
}
edit: At request, adding save Respository pattern, BAsic sample, error handling removed
public class RepositoryBase<TPoco> : where TPoco : BaseObject {
public RepositoryBase(BosBaseDbContext context) { Context = context; }
....
/// <summary>
/// Add new POCO
/// </summary>
public virtual OperationResult Add(TPoco poco) {
var opResult = new OperationResult();
try {
Context.Set<TPoco>().Add(poco);
}
catch (Exception ex) {
.... custom error tool
return opResult;
}
return opResult;
}
/// <summary>
/// Poco must already be attached,, detect chnages is triggered
/// </summary>
public virtual OperationResult Change(TPoco poco) {
var opResult = new OperationResult();
try { // ONLY required if NOT using chnage tracking enabled
Context.ChangeTracker.DetectChanges();
}
catch (Exception ex) {
.... custom error tool
return opResult;
}
return opResult;
}
as i was going through the same issue. So thought to submit my solution.
It will be helpful for the other persons who will be searching-
using (var db = new EFService())
{
var question= db.Question.Single(p => p.QuestionID == QuestionID);
question.Answers.Add(a);
question.Answers.Add(b);
db.SaveChanges();
}

Creating Relationships Between Nodes in Neo4j with Neo4jClient in C#

I'm working with Neo4j using the .Net Neo4jClient (http://hg.readify.net/neo4jclient/wiki/Home). In my code, nodes are airports and relationships are flights.
If I want to create nodes and relationships at the same time, I can do it with the following code:
Classes
public class Airport
{
public string iata { get; set; }
public string name { get; set; }
}
public class flys_toRelationship : Relationship, IRelationshipAllowingSourceNode<Airport>, IRelationshipAllowingTargetNode<Airport>
{
public static readonly string TypeKey = "flys_to";
// Assign Flight Properties
public string flightNumber { get; set; }
public flys_toRelationship(NodeReference targetNode)
: base(targetNode)
{ }
public override string RelationshipTypeKey
{
get { return TypeKey; }
}
}
Main
// Create a New Graph Object
var client = new GraphClient(new Uri("http://localhost:7474/db/data"));
client.Connect();
// Create New Nodes
var lax = client.Create(new Airport() { iata = "lax", name = "Los Angeles International Airport" });
var jfk = client.Create(new Airport() { iata = "jfk", name = "John F. Kennedy International Airport" });
var sfo = client.Create(new Airport() { iata = "sfo", name = "San Francisco International Airport" });
// Create New Relationships
client.CreateRelationship(lax, new flys_toRelationship(jfk) { flightNumber = "1" });
client.CreateRelationship(lax, new flys_toRelationship(sfo) { flightNumber = "2" });
client.CreateRelationship(sfo, new flys_toRelationship(jfk) { flightNumber = "3" });
The problem, however, is when I want to add relationships to already existing nodes. Say I have a graph consisting of only two nodes (airports), say SNA and EWR, and I would like to add a relationship (flight) from SNA to EWR. I try the following and it fails:
// Create a New Graph Object
var client = new GraphClient(new Uri("http://localhost:7474/db/data"));
client.Connect();
Node<Airport> departure = client.QueryIndex<Airport>("node_auto_index", IndexFor.Node, "iata:sna").First();
Node<Airport> arrival = client.QueryIndex<Airport>("node_auto_index", IndexFor.Node, "iata:ewr").First();
//Response.Write(departure.Data.iata); <-- this works fine, btw: it prints "sna"
// Create New Relationships
client.CreateRelationship(departure, new flys_toRelationship(arrival) { flightNumber = "4" });
The two errors I'm receiving are as follows:
1) Argument 1: cannot convert from 'Neo4jClient.Node' to 'Neo4jClient.NodeReference'
2) The type arguments for method 'Neo4jClient.GraphClient.CreateRelationship(Neo4jClient.NodeReference, TRelationship)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
The method the error is referring to is in the following class: http://hg.readify.net/neo4jclient/src/2c5446c17a65d6e5accd420a2dff0089799cbe16/Neo4jClient/GraphClient.cs?at=default
Any ideas?
In your CreateRelationship call you will need to use the node references, not the nodes, so:
client.CreateRelationship(departure.Reference, new flys_toRelationship(arrival.Reference) { flightNumber = "4" });
The reason why your initial creation code works and this didn't is because Create returns you a NodeReference<Airport> (the var is hiding that for you), and the QueryIndex returns a Node<Airport> instance instead.
Neo4jClient predominantly uses NodeReference's for the majority of its operations.
The second error you had was just related to not using the .Reference property as it couldn't determine the types, when you use the .Reference property that error will go away as well.

Categories

Resources