PageRenderTime 50ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/Source/facebook.Linq/Linq/FqlDataContext.cs

https://bitbucket.org/assaframan/facebooklinq
C# | 444 lines | 157 code | 52 blank | 235 comment | 35 complexity | 58238ff7bf849dacf051f6dabb1d4fa7 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5. using System.Data;
  6. using System.Reflection;
  7. using System.Collections;
  8. using facebook;
  9. using System.IO;
  10. namespace facebook.Linq
  11. {
  12. /// <summary>
  13. /// Represents a facebook/application database, as provided by the Facebook FQL API
  14. /// </summary>
  15. public abstract class FqlDataContext : IDisposable
  16. {
  17. protected FqlDataContext(API api)
  18. {
  19. Provider = new FqlProvider(api);
  20. Api = api;
  21. ReuseExistingInstances = true;
  22. }
  23. public API Api { get; private set; }
  24. internal IFqlProvider Provider;
  25. protected FqlTable<T> GetTable<T>()
  26. {
  27. return new FqlTable<T>(this);
  28. }
  29. #region Object Cache (re-uses instances that can be identified, such as user and photos, but not group memberships)
  30. /// <summary>
  31. /// When on, objects that are received from facebook and have an identity field will re-use the same instance to make databinding easier
  32. /// </summary>
  33. public bool ReuseExistingInstances { get; set; }
  34. /// <summary>
  35. /// Gets an object from the database cache given its key. When needed, it also queries facebook for the requested object.
  36. /// </summary>
  37. public virtual T GetObject<T>(object key, bool loadIfNeeded) where T : class // : FacebookObject
  38. {
  39. if (!ReuseExistingInstances || key == null)
  40. return null;
  41. var typ = typeof(T);
  42. if (ObjectCache.ContainsKey(typ))
  43. {
  44. if (ObjectCache[typ].ContainsKey(key))
  45. return ObjectCache[typ][key] as T;
  46. }
  47. if (loadIfNeeded)
  48. {
  49. T item = LoadObject<T>(key);
  50. if (item != null)
  51. StoreObject<T>(item, key);
  52. return item;
  53. }
  54. return null;
  55. }
  56. /// <summary>
  57. /// Stores an object in the database cache.
  58. /// </summary>
  59. public virtual void StoreObject<T>(T o, object key) where T : class// : FacebookObject
  60. {
  61. StoreObject(KnownTypeData.GetTypeData(typeof(T)),o, key);
  62. }
  63. internal virtual void StoreObject(TypeData td, object o, object key)
  64. {
  65. if (!ReuseExistingInstances || key == null)
  66. return;
  67. var typ = td.Type;
  68. if (!ObjectCache.ContainsKey(typ))
  69. ObjectCache.Add(typ, new Dictionary<object, object>());
  70. if (!ObjectCache[typ].ContainsKey(key))
  71. ObjectCache[typ].Add(key, o);
  72. }
  73. //Type / Key / Value
  74. Dictionary<Type, Dictionary<object, object>> ObjectCache = new Dictionary<Type, Dictionary<object, object>>();
  75. /// <summary>
  76. /// Performs a generic FQL query against facebook
  77. /// </summary>
  78. public IEnumerable<T> PerformGenericFql<T>(string fql) where T : class
  79. {
  80. var items = new List<T>();
  81. fql = FixQuery(fql);
  82. var result = Provider.ExecuteFqlQuery(fql);
  83. var reader = new FacebookDataReader(result);
  84. var td = KnownTypeData.GetTypeData(typeof(T));
  85. while (reader.Read())
  86. {
  87. T item = null;
  88. var elem = Activator.CreateInstance<T>();
  89. var fetched = false;
  90. if (td.IdentityProperty != null)
  91. {
  92. var idVal = reader[td.IdentityProperty.FqlFieldName];
  93. item = GetObject<T>(idVal,false);
  94. fetched = item != null;
  95. }
  96. if (!fetched)
  97. {
  98. item = Activator.CreateInstance<T>();
  99. //item.DataContext = this;
  100. if (item != null && td.IdentityProperty != null)
  101. {
  102. StoreObject<T>(item, reader[td.IdentityProperty.FqlFieldName]);
  103. }
  104. }
  105. foreach (var pData in td.Properties)
  106. {
  107. pData.Value.PropertyInfo.SetValue(item, reader[pData.Value.FqlFieldName], null);
  108. }
  109. items.Add(item);
  110. }
  111. return items;
  112. }
  113. internal static string FixQuery(string fql)
  114. {
  115. var words = fql.Split(' ');
  116. if (words.Length < 3)
  117. return fql;
  118. if (words[0].ToLower() != "select")
  119. return fql;
  120. if (words[2].ToLower() != "from")
  121. return fql;
  122. var replacement = words[1];
  123. if (replacement != "*")
  124. return fql;
  125. var tableName = words[3].ToLower();
  126. if (tableName.StartsWith("app."))
  127. tableName = tableName.Substring(4);
  128. var td = KnownTypeData.GetTypeDataByTableName(tableName);
  129. if (td == null)
  130. throw new Exception();
  131. replacement = td.PropertiesByFieldName.Keys.StringConcat(",");
  132. if (replacement.IsNotNullOrEmpty())
  133. words[1] = replacement;
  134. return words.StringConcat(" ");
  135. }
  136. #region Overrides for LinqExtender
  137. public IDataReader ExecuteReader(CommandType cmdType, string sql)
  138. {
  139. var xml = this.Provider.ExecuteFqlQuery(sql);
  140. return new FacebookDataReader(xml);
  141. }
  142. #endregion
  143. private T LoadObject<T>(object key) where T : class //FacebookObject
  144. {
  145. var td = KnownTypeData.GetTypeData(typeof(T));
  146. var column = td.IdentityProperty;
  147. if (column==null)
  148. return null;
  149. var propsCSV = td.Properties.StringConcat(p=>p.Value.FqlFieldName,"",",","");
  150. var fql = String.Format("SELECT {0} from {1} where {2}={3} limit 1",
  151. propsCSV,
  152. td.FqlTableName,
  153. td.IdentityProperty.FqlFieldName,
  154. key);
  155. return (GetType().GetMethod("PerformGenericFql").MakeGenericMethod(typeof(T)).Invoke(this, new object[] { fql }) as IEnumerable<T>).FirstOrDefault();
  156. }
  157. #endregion
  158. public virtual void Dispose()
  159. {
  160. ObjectCache = null;
  161. }
  162. public TextWriter Log
  163. {
  164. get
  165. {
  166. return Provider.Log;
  167. }
  168. set
  169. {
  170. Provider.Log = value;
  171. }
  172. }
  173. }
  174. ///// <summary>
  175. ///// Represents an application database, as provided by the Facebook API
  176. ///// </summary>
  177. //abstract class FqlApplicationDataContext : FqlDataContext
  178. //{
  179. // protected FqlApplicationDataContext(API api)
  180. // : base(api)
  181. // {
  182. // //KnownTypeData.GetTypeData
  183. // }
  184. // public void InsertOnSubmit<T>(T t) where T : class
  185. // {
  186. // if (!PendingObjects.ContainsKey(typeof(T)))
  187. // PendingObjects.Add(typeof(T), new HashSet<object>());
  188. // PendingObjects[typeof(T)].Add(t);
  189. // }
  190. // public void InsertAllOnSubmit<T>(IEnumerable<T> items) where T : class
  191. // {
  192. // if (!PendingObjects.ContainsKey(typeof(T)))
  193. // PendingObjects.Add(typeof(T), new HashSet<object>());
  194. // var l = PendingObjects[typeof(T)];
  195. // foreach(var item in items)
  196. // l.Add(item);
  197. // }
  198. // private Dictionary<string, string> BuildObjectDictionary(TypeData td, object o)
  199. // {
  200. // var ret = new Dictionary<string, string>();
  201. // foreach (var p in td.Properties)
  202. // {
  203. // var fn = p.Value.FqlFieldName;
  204. // var val = p.Value.PropertyInfo.GetValue(o, null); //TODO: Convert to either string or long or null
  205. // if (val == null)
  206. // continue;
  207. // if (!(val is string))
  208. // {
  209. // var l = ConvertToFacebookNumber(val);;
  210. // if (l == 0)
  211. // continue;
  212. // else
  213. // val = l;
  214. // }
  215. // ret.Add(fn, val.ToString());
  216. // }
  217. // return ret;
  218. // }
  219. // private long ConvertToFacebookNumber(object val)
  220. // {
  221. // //enums are not supported
  222. // var vt = val.GetType();
  223. // if (vt.IsEnum)
  224. // {
  225. // throw new Exception("Enums are not supported yet");
  226. // }
  227. // if (vt == typeof(long))
  228. // {
  229. // return (long)val;
  230. // }
  231. // if (vt == typeof(DateTime))
  232. // {
  233. // DateTime? dt = (DateTime)val;
  234. // return Convert.ToInt64(DateHelper.ConvertDateToDouble(dt));
  235. // }
  236. // if (vt == typeof(DateTime?))
  237. // {
  238. // DateTime? dt = (DateTime?)val;
  239. // val = DateHelper.ConvertDateToDouble(dt);
  240. // return (long)val;
  241. // }
  242. // if (vt == typeof(bool))
  243. // {
  244. // bool b = (bool)val;
  245. // return b ? 1 : 0;
  246. // }
  247. // throw new NotImplementedException();
  248. // }
  249. // public void SubmitChanges()
  250. // {
  251. // foreach (var typ in PendingObjects.Keys)
  252. // {
  253. // var td = KnownTypeData.GetTypeData(typ);
  254. // foreach (var o in PendingObjects[typ])
  255. // {
  256. // var dict = BuildObjectDictionary(td, o);
  257. // var newId = Api.data.createObject(td.FqlTableName, dict);
  258. // if (td.IdentityProperty != null && td.IdentityProperty.FqlFieldName=="_id")
  259. // td.IdentityProperty.PropertyInfo.SetValue(o, newId, null);
  260. // StoreObject(td, o, newId);
  261. // }
  262. // PendingObjects[typ].Clear();
  263. // }
  264. // }
  265. // private Dictionary<Type, HashSet<object>> PendingObjects = new Dictionary<Type, HashSet<object>>();
  266. // #region Model Maintenance
  267. // private IEnumerable<PropertyInfo> TableProperties
  268. // {
  269. // get
  270. // {
  271. // return GetType().GetProperties().Where(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(FqlTable<>));
  272. // }
  273. // }
  274. // public void BuildModel()
  275. // {
  276. // BuildModel(false);
  277. // }
  278. // public void BuildModel(bool dropOldModel)
  279. // {
  280. // foreach (var tbl in TableProperties)
  281. // {
  282. // var itemType = tbl.PropertyType.GetGenericArguments()[0];
  283. // var td = KnownTypeData.GetTypeData(itemType);
  284. // var tblName = td.FqlTableName;
  285. // if (dropOldModel)
  286. // {
  287. // try
  288. // {
  289. // Api.data.dropObjectType(tblName);
  290. // }
  291. // catch
  292. // {
  293. // //Drop failed, what can you do..
  294. // }
  295. // }
  296. // Api.data.createObjectType(tblName);
  297. // foreach (var pi in td.Properties)
  298. // {
  299. // var propName = pi.Key;
  300. // var pd = pi.Value;
  301. // //if (dropOldModel && pd.FqlFieldName!="_id")
  302. // //{
  303. // // try
  304. // // {
  305. // // Api.data.undefineObjectProperty(tblName, pd.FqlFieldName);
  306. // // }
  307. // // catch
  308. // // {
  309. // // //Drop failed, what can one do..
  310. // // }
  311. // //}
  312. // var fbPropertyType = GetFacebookDataTypeId(pd.PropertyInfo.PropertyType);
  313. // if (fbPropertyType == 0)
  314. // throw new Exception("Invalid property type: " + pd.PropertyInfo.PropertyType + " for property " + tbl.Name + "." + pd.PropertyInfo.Name);
  315. // if (pd.FqlFieldName == "_id")
  316. // continue;
  317. // Api.data.defineObjectProperty(tblName, pd.FqlFieldName, fbPropertyType);
  318. // }
  319. // }
  320. // }
  321. // public void DeleteModel()
  322. // {
  323. // foreach (var tbl in TableProperties)
  324. // {
  325. // var itemType = tbl.PropertyType.GetGenericArguments()[0];
  326. // var td = KnownTypeData.GetTypeData(itemType);
  327. // var tblName = td.FqlTableName;
  328. // try
  329. // {
  330. // Api.data.dropObjectType(tblName);
  331. // }
  332. // catch
  333. // {
  334. // //Drop failed
  335. // }
  336. // }
  337. // }
  338. // public void VerifyModel()
  339. // {
  340. // throw new NotImplementedException();
  341. // }
  342. // private int GetFacebookDataTypeId(Type t)
  343. // {
  344. // int fbPropertyType = 0;
  345. // //1 - long or nullable long
  346. // //2 - short string
  347. // //3 - long string
  348. // if (t == typeof(long))
  349. // fbPropertyType = 1;
  350. // else if (t == typeof(long?))
  351. // fbPropertyType = 1;
  352. // else if (t == typeof(bool))
  353. // fbPropertyType = 1;
  354. // else if (t == typeof(DateTime))
  355. // fbPropertyType = 1;
  356. // else if (t == typeof(DateTime?))
  357. // fbPropertyType = 1;
  358. // else if (t == typeof(string))
  359. // fbPropertyType = 3;
  360. // else if (t.IsEnum)
  361. // fbPropertyType = 0;
  362. // return fbPropertyType;
  363. // }
  364. // public bool ModelExists
  365. // {
  366. // get
  367. // {
  368. // foreach (var tbl in TableProperties)
  369. // {
  370. // var itemType = tbl.PropertyType.GetGenericArguments()[0];
  371. // var td = KnownTypeData.GetTypeData(itemType);
  372. // var tblName = td.FqlTableName;
  373. // var fbtd = Api.data.getObjectType(tblName);
  374. // foreach (var pi in td.Properties)
  375. // {
  376. // var propName = pi.Key;
  377. // var pd = pi.Value;
  378. // var fbPropertyType = GetFacebookDataTypeId(pd.PropertyInfo.PropertyType);
  379. // if (fbPropertyType==0)
  380. // throw new Exception("Invalid property type: " + pd.PropertyInfo.PropertyType + " for property " + tbl.Name + "." + pd.PropertyInfo.Name);
  381. // if (pd.FqlFieldName == "_id")
  382. // continue;
  383. // if (fbtd.FirstOrDefault(t => t.data_type == fbPropertyType && t.name == pd.FqlFieldName) == null)
  384. // return false;
  385. // }
  386. // }
  387. // return true;
  388. // }
  389. // }
  390. // #endregion
  391. //}
  392. }